commit 93cf9560b13619159efb3ca12eeeb13d6031ad85 (tree)
parent 86b31394c9d73b0f753918cea4f08ce8d7a26119
Author: Michael Dusan <michael.dusan@gmail.com>
Date: Sun, 11 Apr 2021 17:40:19 -0400
Merge remote-tracking branch 'origin/master' into llvm12
Diffstat:
78 files changed, 6701 insertions(+), 4013 deletions(-)
diff --git a/ci/azure/macos_arm64_script b/ci/azure/macos_arm64_script
@@ -114,13 +114,12 @@ if [ "${BUILD_REASON}" != "PullRequest" ]; then
SHASUM=$(shasum -a 256 $TARBALL | cut '-d ' -f1)
BYTESIZE=$(wc -c < $TARBALL)
- JSONFILE="macos-$GITBRANCH.json"
+ JSONFILE="tarball.json"
touch $JSONFILE
echo "{\"tarball\": \"$TARBALL\"," >>$JSONFILE
echo "\"shasum\": \"$SHASUM\"," >>$JSONFILE
echo "\"size\": \"$BYTESIZE\"}" >>$JSONFILE
- s3cmd put -P --add-header="Cache-Control: max-age=0, must-revalidate" "$JSONFILE" "s3://ziglang.org/builds/$JSONFILE"
s3cmd put -P "$JSONFILE" "s3://ziglang.org/builds/$ARCH-macos-$VERSION.json"
# `set -x` causes these variables to be mangled.
diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml
@@ -24,7 +24,7 @@ jobs:
secureFile: s3cfg
- script: ci/azure/macos_arm64_script
name: main
- displayName: 'Build and cross-compile'
+ displayName: 'Build'
- job: BuildLinux
pool:
vmImage: 'ubuntu-18.04'
@@ -66,6 +66,7 @@ jobs:
- job: OnMasterSuccess
dependsOn:
- BuildMacOS
+ - BuildMacOS_arm64
- BuildLinux
- BuildWindows
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
diff --git a/ci/srht/index.html b/ci/srht/index.html
@@ -200,6 +200,12 @@
<td class="code">{{X86_64_MACOS_SHASUM}}</td>
</tr>
<tr>
+ <td><a href="https://ziglang.org/builds/{{AARCH64_MACOS_TARBALL}}">{{AARCH64_MACOS_TARBALL}}</a></td>
+ <td>Binary</td>
+ <td>{{AARCH64_MACOS_BYTESIZE}}</td>
+ <td class="code">{{AARCH64_MACOS_SHASUM}}</td>
+ </tr>
+ <tr>
<td><a href="https://ziglang.org/builds/{{X86_64_FREEBSD_TARBALL}}">{{X86_64_FREEBSD_TARBALL}}</a></td>
<td>Binary</td>
<td>{{X86_64_FREEBSD_BYTESIZE}}</td>
diff --git a/ci/srht/index.json b/ci/srht/index.json
@@ -19,6 +19,11 @@
"shasum": "{{X86_64_MACOS_SHASUM}}",
"size": "{{X86_64_MACOS_BYTESIZE}}"
},
+ "aarch64-macos": {
+ "tarball": "https://ziglang.org/builds/{{AARCH64_MACOS_TARBALL}}",
+ "shasum": "{{AARCH64_MACOS_SHASUM}}",
+ "size": "{{AARCH64_MACOS_BYTESIZE}}"
+ },
"x86_64-windows": {
"tarball": "https://ziglang.org/builds/{{X86_64_WINDOWS_TARBALL}}",
"shasum": "{{X86_64_WINDOWS_SHASUM}}",
diff --git a/ci/srht/update_download_page b/ci/srht/update_download_page
@@ -12,6 +12,7 @@ NATIVE_TARBALL="zig-linux-$(uname -m)-$VERSION.tar.xz"
AARCH64_LINUX_JSON_URL="https://ziglang.org/builds/aarch64-linux-$VERSION.json"
X86_64_LINUX_JSON_URL="https://ziglang.org/builds/x86_64-linux-$VERSION.json"
X86_64_WINDOWS_JSON_URL="https://ziglang.org/builds/x86_64-windows-$VERSION.json"
+AARCH64_MACOS_JSON_URL="https://ziglang.org/builds/aarch64-macos-$VERSION.json"
X86_64_MACOS_JSON_URL="https://ziglang.org/builds/x86_64-macos-$VERSION.json"
X86_64_FREEBSD_JSON_URL="https://ziglang.org/builds/x86_64-freebsd-$VERSION.json"
@@ -20,6 +21,7 @@ X86_64_FREEBSD_JSON_URL="https://ziglang.org/builds/x86_64-freebsd-$VERSION.json
curl --fail -I "$AARCH64_LINUX_JSON_URL" >/dev/null || exit 0
curl --fail -I "$X86_64_LINUX_JSON_URL" >/dev/null || exit 0
curl --fail -I "$X86_64_WINDOWS_JSON_URL" >/dev/null || exit 0
+curl --fail -I "$AARCH64_MACOS_JSON_URL" >/dev/null || exit 0
curl --fail -I "$X86_64_MACOS_JSON_URL" >/dev/null || exit 0
curl --fail -I "$X86_64_FREEBSD_JSON_URL" >/dev/null || exit 0
@@ -57,6 +59,11 @@ export X86_64_WINDOWS_TARBALL="$(echo "$X86_64_WINDOWS_JSON" | jq .tarball -r)"
export X86_64_WINDOWS_BYTESIZE="$(echo "$X86_64_WINDOWS_JSON" | jq .size -r)"
export X86_64_WINDOWS_SHASUM="$(echo "$X86_64_WINDOWS_JSON" | jq .shasum -r)"
+AARCH64_MACOS_JSON=$(curl --fail "$AARCH64_MACOS_JSON_URL" || exit 1)
+export AARCH64_MACOS_TARBALL="$(echo "$AARCH64_MACOS_JSON" | jq .tarball -r)"
+export AARCH64_MACOS_BYTESIZE="$(echo "$AARCH64_MACOS_JSON" | jq .size -r)"
+export AARCH64_MACOS_SHASUM="$(echo "$AARCH64_MACOS_JSON" | jq .shasum -r)"
+
X86_64_MACOS_JSON=$(curl --fail "$X86_64_MACOS_JSON_URL" || exit 1)
export X86_64_MACOS_TARBALL="$(echo "$X86_64_MACOS_JSON" | jq .tarball -r)"
export X86_64_MACOS_BYTESIZE="$(echo "$X86_64_MACOS_JSON" | jq .size -r)"
diff --git a/doc/docgen.zig b/doc/docgen.zig
@@ -1023,6 +1023,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any
const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe);
for (toc.nodes) |node| {
+ defer root_node.completeOne();
switch (node) {
.Content => |data| {
try out.writeAll(data);
@@ -1062,8 +1063,6 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any
try tokenizeAndPrint(tokenizer, out, content_tok);
},
.Code => |code| {
- root_node.completeOne();
-
const raw_source = tokenizer.buffer[code.source_token.start..code.source_token.end];
const trimmed_raw_source = mem.trim(u8, raw_source, " \n");
if (!code.is_inline) {
diff --git a/doc/langref.html.in b/doc/langref.html.in
@@ -85,7 +85,6 @@
#main-wrapper {
display: flex;
flex-direction: column;
- height: 100vh;
}
#contents-wrapper {
@@ -106,6 +105,11 @@
#main-wrapper {
flex-direction: row;
}
+ #toc {
+ height: 100vh;
+ position: sticky;
+ top: 0;
+ }
#contents-wrapper, #toc {
overflow: auto;
}
diff --git a/lib/std/build.zig b/lib/std/build.zig
@@ -1939,6 +1939,15 @@ pub const LibExeObjStep = struct {
out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable;
return;
},
+ ?[:0]const u8 => {
+ out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable;
+ if (value) |payload| {
+ out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable;
+ } else {
+ out.writeAll("null;\n") catch unreachable;
+ }
+ return;
+ },
?[]const u8 => {
out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable;
if (value) |payload| {
diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig
@@ -18,14 +18,14 @@ pub extern "c" fn _lwp_self() lwpid_t;
pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int;
pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;
-pub extern "c" fn __fstat50(fd: fd_t, buf: *Stat) c_int;
-pub extern "c" fn __stat50(path: [*:0]const u8, buf: *Stat) c_int;
+pub extern "c" fn __fstat50(fd: fd_t, buf: *libc_stat) c_int;
+pub extern "c" fn __stat50(path: [*:0]const u8, buf: *libc_stat) c_int;
pub extern "c" fn __clock_gettime50(clk_id: c_int, tp: *timespec) c_int;
pub extern "c" fn __clock_getres50(clk_id: c_int, tp: *timespec) c_int;
pub extern "c" fn __getdents30(fd: c_int, buf_ptr: [*]u8, nbytes: usize) c_int;
pub extern "c" fn __sigaltstack14(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
pub extern "c" fn __nanosleep50(rqtp: *const timespec, rmtp: ?*timespec) c_int;
-pub extern "c" fn __sigaction14(sig: c_int, noalias act: *const Sigaction, noalias oact: ?*Sigaction) c_int;
+pub extern "c" fn __sigaction14(sig: c_int, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) c_int;
pub extern "c" fn __sigprocmask14(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int;
pub extern "c" fn __socket30(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int;
pub extern "c" fn __gettimeofday50(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int;
@@ -34,7 +34,6 @@ pub extern "c" fn __getrusage50(who: c_int, usage: *rusage) c_int;
pub extern "c" fn __libc_thr_yield() c_int;
pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int;
-pub extern "c" fn malloc_usable_size(?*const c_void) usize;
pub const pthread_mutex_t = extern struct {
ptm_magic: u32 = 0x33330003,
@@ -93,3 +92,5 @@ pub const pthread_attr_t = extern struct {
pta_flags: i32,
pta_private: ?*c_void,
};
+
+pub const sem_t = ?*opaque {};
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
@@ -462,7 +462,7 @@ pub const TTY = struct {
// TODO give this a payload of file handle
windows_api,
- fn setColor(conf: Config, out_stream: anytype, color: Color) void {
+ pub fn setColor(conf: Config, out_stream: anytype, color: Color) void {
nosuspend switch (conf) {
.no_color => return,
.escape_codes => switch (color) {
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
@@ -223,15 +223,15 @@ pub const File = struct {
return os.lseek_SET(self.handle, offset);
}
- pub const GetPosError = os.SeekError || os.FStatError;
+ pub const GetSeekPosError = os.SeekError || os.FStatError;
/// TODO: integrate with async I/O
- pub fn getPos(self: File) GetPosError!u64 {
+ pub fn getPos(self: File) GetSeekPosError!u64 {
return os.lseek_CUR_get(self.handle);
}
/// TODO: integrate with async I/O
- pub fn getEndPos(self: File) GetPosError!u64 {
+ pub fn getEndPos(self: File) GetSeekPosError!u64 {
if (builtin.os.tag == .windows) {
return windows.GetFileSizeEx(self.handle);
}
@@ -819,7 +819,7 @@ pub const File = struct {
pub const SeekableStream = io.SeekableStream(
File,
SeekError,
- GetPosError,
+ GetSeekPosError,
seekTo,
seekBy,
getPos,
diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig
@@ -398,10 +398,6 @@ pub fn HashMapUnmanaged(
return size * 100 < max_load_percentage * cap;
}
- pub fn init(allocator: *Allocator) Self {
- return .{};
- }
-
pub fn deinit(self: *Self, allocator: *Allocator) void {
self.deallocate(allocator);
self.* = undefined;
diff --git a/lib/std/io/stream_source.zig b/lib/std/io/stream_source.zig
@@ -5,7 +5,6 @@
// and substantial portions of the software.
const std = @import("../std.zig");
const io = std.io;
-const testing = std.testing;
/// Provides `io.Reader`, `io.Writer`, and `io.SeekableStream` for in-memory buffers as
/// well as files.
@@ -19,7 +18,7 @@ pub const StreamSource = union(enum) {
pub const ReadError = std.fs.File.ReadError;
pub const WriteError = std.fs.File.WriteError;
pub const SeekError = std.fs.File.SeekError;
- pub const GetSeekPosError = std.fs.File.GetPosError;
+ pub const GetSeekPosError = std.fs.File.GetSeekPosError;
pub const Reader = io.Reader(*StreamSource, ReadError, read);
pub const Writer = io.Writer(*StreamSource, WriteError, write);
diff --git a/lib/std/json.zig b/lib/std/json.zig
@@ -1234,7 +1234,7 @@ test "json.validate" {
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const ArrayList = std.ArrayList;
-const StringHashMap = std.StringHashMap;
+const StringArrayHashMap = std.StringArrayHashMap;
pub const ValueTree = struct {
arena: ArenaAllocator,
@@ -1245,7 +1245,7 @@ pub const ValueTree = struct {
}
};
-pub const ObjectMap = StringHashMap(Value);
+pub const ObjectMap = StringArrayHashMap(Value);
pub const Array = ArrayList(Value);
/// Represents a JSON value
diff --git a/lib/std/json/test.zig b/lib/std/json/test.zig
@@ -12,7 +12,7 @@ const std = @import("../std.zig");
const json = std.json;
const testing = std.testing;
-fn testNonStreaming(comptime s: []const u8) !void {
+fn testNonStreaming(s: []const u8) !void {
var p = json.Parser.init(testing.allocator, false);
defer p.deinit();
@@ -20,44 +20,60 @@ fn testNonStreaming(comptime s: []const u8) !void {
defer tree.deinit();
}
-fn ok(comptime s: []const u8) void {
+fn ok(s: []const u8) !void {
testing.expect(json.validate(s));
- testNonStreaming(s) catch testing.expect(false);
+ try testNonStreaming(s);
}
-fn err(comptime s: []const u8) void {
+fn err(s: []const u8) void {
testing.expect(!json.validate(s));
testNonStreaming(s) catch return;
testing.expect(false);
}
-fn utf8Error(comptime s: []const u8) void {
+fn utf8Error(s: []const u8) void {
testing.expect(!json.validate(s));
testing.expectError(error.InvalidUtf8Byte, testNonStreaming(s));
}
-fn any(comptime s: []const u8) void {
+fn any(s: []const u8) void {
_ = json.validate(s);
testNonStreaming(s) catch {};
}
-fn anyStreamingErrNonStreaming(comptime s: []const u8) void {
+fn anyStreamingErrNonStreaming(s: []const u8) void {
_ = json.validate(s);
testNonStreaming(s) catch return;
testing.expect(false);
}
+fn roundTrip(s: []const u8) !void {
+ testing.expect(json.validate(s));
+
+ var p = json.Parser.init(testing.allocator, false);
+ defer p.deinit();
+
+ var tree = try p.parse(s);
+ defer tree.deinit();
+
+ var buf: [256]u8 = undefined;
+ var fbs = std.io.fixedBufferStream(&buf);
+ try tree.root.jsonStringify(.{}, fbs.writer());
+
+ testing.expectEqualStrings(s, fbs.getWritten());
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Additional tests not part of test JSONTestSuite.
test "y_trailing_comma_after_empty" {
- ok(
+ try roundTrip(
\\{"1":[],"2":{},"3":"4"}
);
}
@@ -65,252 +81,252 @@ test "y_trailing_comma_after_empty" {
////////////////////////////////////////////////////////////////////////////////////////////////////
test "y_array_arraysWithSpaces" {
- ok(
+ try ok(
\\[[] ]
);
}
test "y_array_empty" {
- ok(
+ try roundTrip(
\\[]
);
}
test "y_array_empty-string" {
- ok(
+ try roundTrip(
\\[""]
);
}
test "y_array_ending_with_newline" {
- ok(
+ try roundTrip(
\\["a"]
);
}
test "y_array_false" {
- ok(
+ try roundTrip(
\\[false]
);
}
test "y_array_heterogeneous" {
- ok(
+ try ok(
\\[null, 1, "1", {}]
);
}
test "y_array_null" {
- ok(
+ try roundTrip(
\\[null]
);
}
test "y_array_with_1_and_newline" {
- ok(
+ try ok(
\\[1
\\]
);
}
test "y_array_with_leading_space" {
- ok(
+ try ok(
\\ [1]
);
}
test "y_array_with_several_null" {
- ok(
+ try roundTrip(
\\[1,null,null,null,2]
);
}
test "y_array_with_trailing_space" {
- ok("[2] ");
+ try ok("[2] ");
}
test "y_number_0e+1" {
- ok(
+ try ok(
\\[0e+1]
);
}
test "y_number_0e1" {
- ok(
+ try ok(
\\[0e1]
);
}
test "y_number_after_space" {
- ok(
+ try ok(
\\[ 4]
);
}
test "y_number_double_close_to_zero" {
- ok(
+ try ok(
\\[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]
);
}
test "y_number_int_with_exp" {
- ok(
+ try ok(
\\[20e1]
);
}
test "y_number" {
- ok(
+ try ok(
\\[123e65]
);
}
test "y_number_minus_zero" {
- ok(
+ try ok(
\\[-0]
);
}
test "y_number_negative_int" {
- ok(
+ try roundTrip(
\\[-123]
);
}
test "y_number_negative_one" {
- ok(
+ try roundTrip(
\\[-1]
);
}
test "y_number_negative_zero" {
- ok(
+ try ok(
\\[-0]
);
}
test "y_number_real_capital_e" {
- ok(
+ try ok(
\\[1E22]
);
}
test "y_number_real_capital_e_neg_exp" {
- ok(
+ try ok(
\\[1E-2]
);
}
test "y_number_real_capital_e_pos_exp" {
- ok(
+ try ok(
\\[1E+2]
);
}
test "y_number_real_exponent" {
- ok(
+ try ok(
\\[123e45]
);
}
test "y_number_real_fraction_exponent" {
- ok(
+ try ok(
\\[123.456e78]
);
}
test "y_number_real_neg_exp" {
- ok(
+ try ok(
\\[1e-2]
);
}
test "y_number_real_pos_exponent" {
- ok(
+ try ok(
\\[1e+2]
);
}
test "y_number_simple_int" {
- ok(
+ try roundTrip(
\\[123]
);
}
test "y_number_simple_real" {
- ok(
+ try ok(
\\[123.456789]
);
}
test "y_object_basic" {
- ok(
+ try roundTrip(
\\{"asd":"sdf"}
);
}
test "y_object_duplicated_key_and_value" {
- ok(
+ try ok(
\\{"a":"b","a":"b"}
);
}
test "y_object_duplicated_key" {
- ok(
+ try ok(
\\{"a":"b","a":"c"}
);
}
test "y_object_empty" {
- ok(
+ try roundTrip(
\\{}
);
}
test "y_object_empty_key" {
- ok(
+ try roundTrip(
\\{"":0}
);
}
test "y_object_escaped_null_in_key" {
- ok(
+ try ok(
\\{"foo\u0000bar": 42}
);
}
test "y_object_extreme_numbers" {
- ok(
+ try ok(
\\{ "min": -1.0e+28, "max": 1.0e+28 }
);
}
test "y_object" {
- ok(
+ try ok(
\\{"asd":"sdf", "dfg":"fgh"}
);
}
test "y_object_long_strings" {
- ok(
+ try ok(
\\{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
);
}
test "y_object_simple" {
- ok(
+ try roundTrip(
\\{"a":[]}
);
}
test "y_object_string_unicode" {
- ok(
+ try ok(
\\{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" }
);
}
test "y_object_with_newlines" {
- ok(
+ try ok(
\\{
\\"a": "b"
\\}
@@ -318,311 +334,311 @@ test "y_object_with_newlines" {
}
test "y_string_1_2_3_bytes_UTF-8_sequences" {
- ok(
+ try ok(
\\["\u0060\u012a\u12AB"]
);
}
test "y_string_accepted_surrogate_pair" {
- ok(
+ try ok(
\\["\uD801\udc37"]
);
}
test "y_string_accepted_surrogate_pairs" {
- ok(
+ try ok(
\\["\ud83d\ude39\ud83d\udc8d"]
);
}
test "y_string_allowed_escapes" {
- ok(
+ try ok(
\\["\"\\\/\b\f\n\r\t"]
);
}
test "y_string_backslash_and_u_escaped_zero" {
- ok(
+ try ok(
\\["\\u0000"]
);
}
test "y_string_backslash_doublequotes" {
- ok(
+ try roundTrip(
\\["\""]
);
}
test "y_string_comments" {
- ok(
+ try ok(
\\["a/*b*/c/*d//e"]
);
}
test "y_string_double_escape_a" {
- ok(
+ try ok(
\\["\\a"]
);
}
test "y_string_double_escape_n" {
- ok(
+ try roundTrip(
\\["\\n"]
);
}
test "y_string_escaped_control_character" {
- ok(
+ try ok(
\\["\u0012"]
);
}
test "y_string_escaped_noncharacter" {
- ok(
+ try ok(
\\["\uFFFF"]
);
}
test "y_string_in_array" {
- ok(
+ try ok(
\\["asd"]
);
}
test "y_string_in_array_with_leading_space" {
- ok(
+ try ok(
\\[ "asd"]
);
}
test "y_string_last_surrogates_1_and_2" {
- ok(
+ try ok(
\\["\uDBFF\uDFFF"]
);
}
test "y_string_nbsp_uescaped" {
- ok(
+ try ok(
\\["new\u00A0line"]
);
}
test "y_string_nonCharacterInUTF-8_U+10FFFF" {
- ok(
+ try ok(
\\[""]
);
}
test "y_string_nonCharacterInUTF-8_U+FFFF" {
- ok(
+ try ok(
\\[""]
);
}
test "y_string_null_escape" {
- ok(
+ try ok(
\\["\u0000"]
);
}
test "y_string_one-byte-utf-8" {
- ok(
+ try ok(
\\["\u002c"]
);
}
test "y_string_pi" {
- ok(
+ try ok(
\\["π"]
);
}
test "y_string_reservedCharacterInUTF-8_U+1BFFF" {
- ok(
+ try ok(
\\[""]
);
}
test "y_string_simple_ascii" {
- ok(
+ try ok(
\\["asd "]
);
}
test "y_string_space" {
- ok(
+ try roundTrip(
\\" "
);
}
test "y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF" {
- ok(
+ try ok(
\\["\uD834\uDd1e"]
);
}
test "y_string_three-byte-utf-8" {
- ok(
+ try ok(
\\["\u0821"]
);
}
test "y_string_two-byte-utf-8" {
- ok(
+ try ok(
\\["\u0123"]
);
}
test "y_string_u+2028_line_sep" {
- ok("[\"\xe2\x80\xa8\"]");
+ try ok("[\"\xe2\x80\xa8\"]");
}
test "y_string_u+2029_par_sep" {
- ok("[\"\xe2\x80\xa9\"]");
+ try ok("[\"\xe2\x80\xa9\"]");
}
test "y_string_uescaped_newline" {
- ok(
+ try ok(
\\["new\u000Aline"]
);
}
test "y_string_uEscape" {
- ok(
+ try ok(
\\["\u0061\u30af\u30EA\u30b9"]
);
}
test "y_string_unescaped_char_delete" {
- ok("[\"\x7f\"]");
+ try ok("[\"\x7f\"]");
}
test "y_string_unicode_2" {
- ok(
+ try ok(
\\["⍂㈴⍂"]
);
}
test "y_string_unicodeEscapedBackslash" {
- ok(
+ try ok(
\\["\u005C"]
);
}
test "y_string_unicode_escaped_double_quote" {
- ok(
+ try ok(
\\["\u0022"]
);
}
test "y_string_unicode" {
- ok(
+ try ok(
\\["\uA66D"]
);
}
test "y_string_unicode_U+10FFFE_nonchar" {
- ok(
+ try ok(
\\["\uDBFF\uDFFE"]
);
}
test "y_string_unicode_U+1FFFE_nonchar" {
- ok(
+ try ok(
\\["\uD83F\uDFFE"]
);
}
test "y_string_unicode_U+200B_ZERO_WIDTH_SPACE" {
- ok(
+ try ok(
\\["\u200B"]
);
}
test "y_string_unicode_U+2064_invisible_plus" {
- ok(
+ try ok(
\\["\u2064"]
);
}
test "y_string_unicode_U+FDD0_nonchar" {
- ok(
+ try ok(
\\["\uFDD0"]
);
}
test "y_string_unicode_U+FFFE_nonchar" {
- ok(
+ try ok(
\\["\uFFFE"]
);
}
test "y_string_utf8" {
- ok(
+ try ok(
\\["€𝄞"]
);
}
test "y_string_with_del_character" {
- ok("[\"a\x7fa\"]");
+ try ok("[\"a\x7fa\"]");
}
test "y_structure_lonely_false" {
- ok(
+ try roundTrip(
\\false
);
}
test "y_structure_lonely_int" {
- ok(
+ try roundTrip(
\\42
);
}
test "y_structure_lonely_negative_real" {
- ok(
+ try ok(
\\-0.1
);
}
test "y_structure_lonely_null" {
- ok(
+ try roundTrip(
\\null
);
}
test "y_structure_lonely_string" {
- ok(
+ try roundTrip(
\\"asd"
);
}
test "y_structure_lonely_true" {
- ok(
+ try roundTrip(
\\true
);
}
test "y_structure_string_empty" {
- ok(
+ try roundTrip(
\\""
);
}
test "y_structure_trailing_newline" {
- ok(
+ try roundTrip(
\\["a"]
);
}
test "y_structure_true_in_array" {
- ok(
+ try roundTrip(
\\[true]
);
}
test "y_structure_whitespace_array" {
- ok(" [] ");
+ try ok(" [] ");
}
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/lib/std/math/ln.zig b/lib/std/math/ln.zig
@@ -36,8 +36,9 @@ pub fn ln(x: anytype) @TypeOf(x) {
.ComptimeInt => {
return @as(comptime_int, math.floor(ln_64(@as(f64, x))));
},
- .Int => {
- return @as(T, math.floor(ln_64(@as(f64, x))));
+ .Int => |IntType| switch (IntType.signedness) {
+ .signed => return @compileError("ln not implemented for signed integers"),
+ .unsigned => return @as(T, math.floor(ln_64(@as(f64, x)))),
},
else => @compileError("ln not implemented for " ++ @typeName(T)),
}
diff --git a/lib/std/math/log.zig b/lib/std/math/log.zig
@@ -31,9 +31,11 @@ pub fn log(comptime T: type, base: T, x: T) T {
.ComptimeInt => {
return @as(comptime_int, math.floor(math.ln(@as(f64, x)) / math.ln(float_base)));
},
- .Int => {
- // TODO implement integer log without using float math
- return @floatToInt(T, math.floor(math.ln(@intToFloat(f64, x)) / math.ln(float_base)));
+
+ // TODO implement integer log without using float math
+ .Int => |IntType| switch (IntType.signedness) {
+ .signed => return @compileError("log not implemented for signed integers"),
+ .unsigned => return @floatToInt(T, math.floor(math.ln(@intToFloat(f64, x)) / math.ln(float_base))),
},
.Float => {
@@ -53,7 +55,7 @@ pub fn log(comptime T: type, base: T, x: T) T {
test "math.log integer" {
expect(log(u8, 2, 0x1) == 0);
expect(log(u8, 2, 0x2) == 1);
- expect(log(i16, 2, 0x72) == 6);
+ expect(log(u16, 2, 0x72) == 6);
expect(log(u32, 2, 0xFFFFFF) == 23);
expect(log(u64, 2, 0x7FF0123456789ABC) == 62);
}
diff --git a/lib/std/math/log10.zig b/lib/std/math/log10.zig
@@ -37,8 +37,9 @@ pub fn log10(x: anytype) @TypeOf(x) {
.ComptimeInt => {
return @as(comptime_int, math.floor(log10_64(@as(f64, x))));
},
- .Int => {
- return @floatToInt(T, math.floor(log10_64(@intToFloat(f64, x))));
+ .Int => |IntType| switch (IntType.signedness) {
+ .signed => return @compileError("log10 not implemented for signed integers"),
+ .unsigned => return @floatToInt(T, math.floor(log10_64(@intToFloat(f64, x)))),
},
else => @compileError("log10 not implemented for " ++ @typeName(T)),
}
diff --git a/lib/std/math/log2.zig b/lib/std/math/log2.zig
@@ -43,8 +43,9 @@ pub fn log2(x: anytype) @TypeOf(x) {
}) : (result += 1) {}
return result;
},
- .Int => {
- return math.log2_int(T, x);
+ .Int => |IntType| switch (IntType.signedness) {
+ .signed => return @compileError("log2 not implemented for signed integers"),
+ .unsigned => return math.log2_int(T, x),
},
else => @compileError("log2 not implemented for " ++ @typeName(T)),
}
diff --git a/lib/std/math/sqrt.zig b/lib/std/math/sqrt.zig
@@ -31,7 +31,10 @@ pub fn sqrt(x: anytype) Sqrt(@TypeOf(x)) {
}
return @as(T, sqrt_int(u128, x));
},
- .Int => return sqrt_int(T, x),
+ .Int => |IntType| switch (IntType.signedness) {
+ .signed => return @compileError("sqrt not implemented for signed integers"),
+ .unsigned => return sqrt_int(T, x),
+ },
else => @compileError("sqrt not implemented for " ++ @typeName(T)),
}
}
diff --git a/lib/std/meta.zig b/lib/std/meta.zig
@@ -970,12 +970,15 @@ fn castPtr(comptime DestType: type, target: anytype) DestType {
if (source.is_const and !dest.is_const or source.is_volatile and !dest.is_volatile)
return @intToPtr(DestType, @ptrToInt(target))
+ else if (@typeInfo(dest.child) == .Opaque)
+ // dest.alignment would error out
+ return @ptrCast(DestType, target)
else
return @ptrCast(DestType, @alignCast(dest.alignment, target));
}
fn ptrInfo(comptime PtrType: type) TypeInfo.Pointer {
- return switch(@typeInfo(PtrType)){
+ return switch (@typeInfo(PtrType)) {
.Optional => |opt_info| @typeInfo(opt_info.child).Pointer,
.Pointer => |ptr_info| ptr_info,
else => unreachable,
@@ -1010,6 +1013,8 @@ test "std.meta.cast" {
testing.expectEqual(@intToPtr(*u8, 2), cast(*u8, @intToPtr(*const u8, 2)));
testing.expectEqual(@intToPtr(*u8, 2), cast(*u8, @intToPtr(*volatile u8, 2)));
+
+ testing.expectEqual(@intToPtr(?*c_void, 2), cast(?*c_void, @intToPtr(*u8, 2)));
}
/// Given a value returns its size as C's sizeof operator would.
@@ -1297,3 +1302,35 @@ pub fn globalOption(comptime name: []const u8, comptime T: type) ?T {
return null;
return @as(T, @field(root, name));
}
+
+/// This function is for translate-c and is not intended for general use.
+/// Convert from clang __builtin_shufflevector index to Zig @shuffle index
+/// clang requires __builtin_shufflevector index arguments to be integer constants.
+/// negative values for `this_index` indicate "don't care" so we arbitrarily choose 0
+/// clang enforces that `this_index` is less than the total number of vector elements
+/// See https://ziglang.org/documentation/master/#shuffle
+/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector
+pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 {
+ if (this_index <= 0) return 0;
+
+ const positive_index = @intCast(usize, this_index);
+ if (positive_index < source_vector_len) return @intCast(i32, this_index);
+ const b_index = positive_index - source_vector_len;
+ return ~@intCast(i32, b_index);
+}
+
+test "shuffleVectorIndex" {
+ const vector_len: usize = 4;
+
+ testing.expect(shuffleVectorIndex(-1, vector_len) == 0);
+
+ testing.expect(shuffleVectorIndex(0, vector_len) == 0);
+ testing.expect(shuffleVectorIndex(1, vector_len) == 1);
+ testing.expect(shuffleVectorIndex(2, vector_len) == 2);
+ testing.expect(shuffleVectorIndex(3, vector_len) == 3);
+
+ testing.expect(shuffleVectorIndex(4, vector_len) == -1);
+ testing.expect(shuffleVectorIndex(5, vector_len) == -2);
+ testing.expect(shuffleVectorIndex(6, vector_len) == -3);
+ testing.expect(shuffleVectorIndex(7, vector_len) == -4);
+}
diff --git a/lib/std/os.zig b/lib/std/os.zig
@@ -3254,6 +3254,9 @@ pub const ConnectError = error{
/// Connection was reset by peer before connect could complete.
ConnectionResetByPeer,
+
+ /// Socket is non-blocking and already has a pending connection in progress.
+ ConnectionPending,
} || UnexpectedError;
/// Initiate a connection on a socket.
@@ -3294,7 +3297,7 @@ pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) Conne
EADDRNOTAVAIL => return error.AddressNotAvailable,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
EAGAIN, EINPROGRESS => return error.WouldBlock,
- EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
+ EALREADY => return error.ConnectionPending,
EBADF => unreachable, // sockfd is not a valid open file descriptor.
ECONNREFUSED => return error.ConnectionRefused,
ECONNRESET => return error.ConnectionResetByPeer,
@@ -3325,7 +3328,7 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void {
EADDRNOTAVAIL => return error.AddressNotAvailable,
EAFNOSUPPORT => return error.AddressFamilyNotSupported,
EAGAIN => return error.SystemResources,
- EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
+ EALREADY => return error.ConnectionPending,
EBADF => unreachable, // sockfd is not a valid open file descriptor.
ECONNREFUSED => return error.ConnectionRefused,
EFAULT => unreachable, // The socket structure address is outside the user's address space.
diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig
@@ -21,6 +21,7 @@ pub usingnamespace switch (builtin.arch) {
.riscv64 => @import("linux/riscv64.zig"),
.sparcv9 => @import("linux/sparc64.zig"),
.mips, .mipsel => @import("linux/mips.zig"),
+ .powerpc => @import("linux/powerpc.zig"),
.powerpc64, .powerpc64le => @import("linux/powerpc64.zig"),
else => struct {},
};
@@ -586,6 +587,92 @@ pub const IP_DEFAULT_MULTICAST_TTL = 1;
pub const IP_DEFAULT_MULTICAST_LOOP = 1;
pub const IP_MAX_MEMBERSHIPS = 20;
+// IPv6 socket options
+
+pub const IPV6_ADDRFORM = 1;
+pub const IPV6_2292PKTINFO = 2;
+pub const IPV6_2292HOPOPTS = 3;
+pub const IPV6_2292DSTOPTS = 4;
+pub const IPV6_2292RTHDR = 5;
+pub const IPV6_2292PKTOPTIONS = 6;
+pub const IPV6_CHECKSUM = 7;
+pub const IPV6_2292HOPLIMIT = 8;
+pub const IPV6_NEXTHOP = 9;
+pub const IPV6_AUTHHDR = 10;
+pub const IPV6_FLOWINFO = 11;
+
+pub const IPV6_UNICAST_HOPS = 16;
+pub const IPV6_MULTICAST_IF = 17;
+pub const IPV6_MULTICAST_HOPS = 18;
+pub const IPV6_MULTICAST_LOOP = 19;
+pub const IPV6_ADD_MEMBERSHIP = 20;
+pub const IPV6_DROP_MEMBERSHIP = 21;
+pub const IPV6_ROUTER_ALERT = 22;
+pub const IPV6_MTU_DISCOVER = 23;
+pub const IPV6_MTU = 24;
+pub const IPV6_RECVERR = 25;
+pub const IPV6_V6ONLY = 26;
+pub const IPV6_JOIN_ANYCAST = 27;
+pub const IPV6_LEAVE_ANYCAST = 28;
+
+// IPV6_MTU_DISCOVER values
+pub const IPV6_PMTUDISC_DONT = 0;
+pub const IPV6_PMTUDISC_WANT = 1;
+pub const IPV6_PMTUDISC_DO = 2;
+pub const IPV6_PMTUDISC_PROBE = 3;
+pub const IPV6_PMTUDISC_INTERFACE = 4;
+pub const IPV6_PMTUDISC_OMIT = 5;
+
+// Flowlabel
+pub const IPV6_FLOWLABEL_MGR = 32;
+pub const IPV6_FLOWINFO_SEND = 33;
+pub const IPV6_IPSEC_POLICY = 34;
+pub const IPV6_XFRM_POLICY = 35;
+pub const IPV6_HDRINCL = 36;
+
+// Advanced API (RFC3542) (1)
+pub const IPV6_RECVPKTINFO = 49;
+pub const IPV6_PKTINFO = 50;
+pub const IPV6_RECVHOPLIMIT = 51;
+pub const IPV6_HOPLIMIT = 52;
+pub const IPV6_RECVHOPOPTS = 53;
+pub const IPV6_HOPOPTS = 54;
+pub const IPV6_RTHDRDSTOPTS = 55;
+pub const IPV6_RECVRTHDR = 56;
+pub const IPV6_RTHDR = 57;
+pub const IPV6_RECVDSTOPTS = 58;
+pub const IPV6_DSTOPTS = 59;
+pub const IPV6_RECVPATHMTU = 60;
+pub const IPV6_PATHMTU = 61;
+pub const IPV6_DONTFRAG = 62;
+
+// Advanced API (RFC3542) (2)
+pub const IPV6_RECVTCLASS = 66;
+pub const IPV6_TCLASS = 67;
+
+pub const IPV6_AUTOFLOWLABEL = 70;
+
+// RFC5014: Source address selection
+pub const IPV6_ADDR_PREFERENCES = 72;
+
+pub const IPV6_PREFER_SRC_TMP = 0x0001;
+pub const IPV6_PREFER_SRC_PUBLIC = 0x0002;
+pub const IPV6_PREFER_SRC_PUBTMP_DEFAULT = 0x0100;
+pub const IPV6_PREFER_SRC_COA = 0x0004;
+pub const IPV6_PREFER_SRC_HOME = 0x0400;
+pub const IPV6_PREFER_SRC_CGA = 0x0008;
+pub const IPV6_PREFER_SRC_NONCGA = 0x0800;
+
+// RFC5082: Generalized Ttl Security Mechanism
+pub const IPV6_MINHOPCOUNT = 73;
+
+pub const IPV6_ORIGDSTADDR = 74;
+pub const IPV6_RECVORIGDSTADDR = IPV6_ORIGDSTADDR;
+pub const IPV6_TRANSPARENT = 75;
+pub const IPV6_UNICAST_IF = 76;
+pub const IPV6_RECVFRAGSIZE = 77;
+pub const IPV6_FREEBIND = 78;
+
pub const MSG_OOB = 0x0001;
pub const MSG_PEEK = 0x0002;
pub const MSG_DONTROUTE = 0x0004;
diff --git a/lib/std/os/bits/linux/powerpc.zig b/lib/std/os/bits/linux/powerpc.zig
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+
+const std = @import("../../../std.zig");
+const linux = std.os.linux;
+const socklen_t = linux.socklen_t;
+const iovec = linux.iovec;
+const iovec_const = linux.iovec_const;
+const uid_t = linux.uid_t;
+const gid_t = linux.gid_t;
+const pid_t = linux.pid_t;
+const stack_t = linux.stack_t;
+const sigset_t = linux.sigset_t;
+pub const SYS = extern enum(usize) {
+ restart_syscall = 0,
+ exit = 1,
+ fork = 2,
+ read = 3,
+ write = 4,
+ open = 5,
+ close = 6,
+ waitpid = 7,
+ creat = 8,
+ link = 9,
+ unlink = 10,
+ execve = 11,
+ chdir = 12,
+ time = 13,
+ mknod = 14,
+ chmod = 15,
+ lchown = 16,
+ @"break" = 17,
+ oldstat = 18,
+ lseek = 19,
+ getpid = 20,
+ mount = 21,
+ umount = 22,
+ setuid = 23,
+ getuid = 24,
+ stime = 25,
+ ptrace = 26,
+ alarm = 27,
+ oldfstat = 28,
+ pause = 29,
+ utime = 30,
+ stty = 31,
+ gtty = 32,
+ access = 33,
+ nice = 34,
+ ftime = 35,
+ sync = 36,
+ kill = 37,
+ rename = 38,
+ mkdir = 39,
+ rmdir = 40,
+ dup = 41,
+ pipe = 42,
+ times = 43,
+ prof = 44,
+ brk = 45,
+ setgid = 46,
+ getgid = 47,
+ signal = 48,
+ geteuid = 49,
+ getegid = 50,
+ acct = 51,
+ umount2 = 52,
+ lock = 53,
+ ioctl = 54,
+ fcntl = 55,
+ mpx = 56,
+ setpgid = 57,
+ ulimit = 58,
+ oldolduname = 59,
+ umask = 60,
+ chroot = 61,
+ ustat = 62,
+ dup2 = 63,
+ getppid = 64,
+ getpgrp = 65,
+ setsid = 66,
+ sigaction = 67,
+ sgetmask = 68,
+ ssetmask = 69,
+ setreuid = 70,
+ setregid = 71,
+ sigsuspend = 72,
+ sigpending = 73,
+ sethostname = 74,
+ setrlimit = 75,
+ getrlimit = 76,
+ getrusage = 77,
+ gettimeofday = 78,
+ settimeofday = 79,
+ getgroups = 80,
+ setgroups = 81,
+ select = 82,
+ symlink = 83,
+ oldlstat = 84,
+ readlink = 85,
+ uselib = 86,
+ swapon = 87,
+ reboot = 88,
+ readdir = 89,
+ mmap = 90,
+ munmap = 91,
+ truncate = 92,
+ ftruncate = 93,
+ fchmod = 94,
+ fchown = 95,
+ getpriority = 96,
+ setpriority = 97,
+ profil = 98,
+ statfs = 99,
+ fstatfs = 100,
+ ioperm = 101,
+ socketcall = 102,
+ syslog = 103,
+ setitimer = 104,
+ getitimer = 105,
+ stat = 106,
+ lstat = 107,
+ fstat = 108,
+ olduname = 109,
+ iopl = 110,
+ vhangup = 111,
+ idle = 112,
+ vm86 = 113,
+ wait4 = 114,
+ swapoff = 115,
+ sysinfo = 116,
+ ipc = 117,
+ fsync = 118,
+ sigreturn = 119,
+ clone = 120,
+ setdomainname = 121,
+ uname = 122,
+ modify_ldt = 123,
+ adjtimex = 124,
+ mprotect = 125,
+ sigprocmask = 126,
+ create_module = 127,
+ init_module = 128,
+ delete_module = 129,
+ get_kernel_syms = 130,
+ quotactl = 131,
+ getpgid = 132,
+ fchdir = 133,
+ bdflush = 134,
+ sysfs = 135,
+ personality = 136,
+ afs_syscall = 137,
+ setfsuid = 138,
+ setfsgid = 139,
+ _llseek = 140,
+ getdents = 141,
+ _newselect = 142,
+ flock = 143,
+ msync = 144,
+ readv = 145,
+ writev = 146,
+ getsid = 147,
+ fdatasync = 148,
+ _sysctl = 149,
+ mlock = 150,
+ munlock = 151,
+ mlockall = 152,
+ munlockall = 153,
+ sched_setparam = 154,
+ sched_getparam = 155,
+ sched_setscheduler = 156,
+ sched_getscheduler = 157,
+ sched_yield = 158,
+ sched_get_priority_max = 159,
+ sched_get_priority_min = 160,
+ sched_rr_get_interval = 161,
+ nanosleep = 162,
+ mremap = 163,
+ setresuid = 164,
+ getresuid = 165,
+ query_module = 166,
+ poll = 167,
+ nfsservctl = 168,
+ setresgid = 169,
+ getresgid = 170,
+ prctl = 171,
+ rt_sigreturn = 172,
+ rt_sigaction = 173,
+ rt_sigprocmask = 174,
+ rt_sigpending = 175,
+ rt_sigtimedwait = 176,
+ rt_sigqueueinfo = 177,
+ rt_sigsuspend = 178,
+ pread64 = 179,
+ pwrite64 = 180,
+ chown = 181,
+ getcwd = 182,
+ capget = 183,
+ capset = 184,
+ sigaltstack = 185,
+ sendfile = 186,
+ getpmsg = 187,
+ putpmsg = 188,
+ vfork = 189,
+ ugetrlimit = 190,
+ readahead = 191,
+ mmap2 = 192,
+ truncate64 = 193,
+ ftruncate64 = 194,
+ stat64 = 195,
+ lstat64 = 196,
+ fstat64 = 197,
+ pciconfig_read = 198,
+ pciconfig_write = 199,
+ pciconfig_iobase = 200,
+ multiplexer = 201,
+ getdents64 = 202,
+ pivot_root = 203,
+ fcntl64 = 204,
+ madvise = 205,
+ mincore = 206,
+ gettid = 207,
+ tkill = 208,
+ setxattr = 209,
+ lsetxattr = 210,
+ fsetxattr = 211,
+ getxattr = 212,
+ lgetxattr = 213,
+ fgetxattr = 214,
+ listxattr = 215,
+ llistxattr = 216,
+ flistxattr = 217,
+ removexattr = 218,
+ lremovexattr = 219,
+ fremovexattr = 220,
+ futex = 221,
+ sched_setaffinity = 222,
+ sched_getaffinity = 223,
+ tuxcall = 225,
+ sendfile64 = 226,
+ io_setup = 227,
+ io_destroy = 228,
+ io_getevents = 229,
+ io_submit = 230,
+ io_cancel = 231,
+ set_tid_address = 232,
+ fadvise64 = 233,
+ exit_group = 234,
+ lookup_dcookie = 235,
+ epoll_create = 236,
+ epoll_ctl = 237,
+ epoll_wait = 238,
+ remap_file_pages = 239,
+ timer_create = 240,
+ timer_settime = 241,
+ timer_gettime = 242,
+ timer_getoverrun = 243,
+ timer_delete = 244,
+ clock_settime = 245,
+ clock_gettime = 246,
+ clock_getres = 247,
+ clock_nanosleep = 248,
+ swapcontext = 249,
+ tgkill = 250,
+ utimes = 251,
+ statfs64 = 252,
+ fstatfs64 = 253,
+ fadvise64_64 = 254,
+ rtas = 255,
+ sys_debug_setcontext = 256,
+ migrate_pages = 258,
+ mbind = 259,
+ get_mempolicy = 260,
+ set_mempolicy = 261,
+ mq_open = 262,
+ mq_unlink = 263,
+ mq_timedsend = 264,
+ mq_timedreceive = 265,
+ mq_notify = 266,
+ mq_getsetattr = 267,
+ kexec_load = 268,
+ add_key = 269,
+ request_key = 270,
+ keyctl = 271,
+ waitid = 272,
+ ioprio_set = 273,
+ ioprio_get = 274,
+ inotify_init = 275,
+ inotify_add_watch = 276,
+ inotify_rm_watch = 277,
+ spu_run = 278,
+ spu_create = 279,
+ pselect6 = 280,
+ ppoll = 281,
+ unshare = 282,
+ splice = 283,
+ tee = 284,
+ vmsplice = 285,
+ openat = 286,
+ mkdirat = 287,
+ mknodat = 288,
+ fchownat = 289,
+ futimesat = 290,
+ fstatat64 = 291,
+ unlinkat = 292,
+ renameat = 293,
+ linkat = 294,
+ symlinkat = 295,
+ readlinkat = 296,
+ fchmodat = 297,
+ faccessat = 298,
+ get_robust_list = 299,
+ set_robust_list = 300,
+ move_pages = 301,
+ getcpu = 302,
+ epoll_pwait = 303,
+ utimensat = 304,
+ signalfd = 305,
+ timerfd_create = 306,
+ eventfd = 307,
+ sync_file_range2 = 308,
+ fallocate = 309,
+ subpage_prot = 310,
+ timerfd_settime = 311,
+ timerfd_gettime = 312,
+ signalfd4 = 313,
+ eventfd2 = 314,
+ epoll_create1 = 315,
+ dup3 = 316,
+ pipe2 = 317,
+ inotify_init1 = 318,
+ perf_event_open = 319,
+ preadv = 320,
+ pwritev = 321,
+ rt_tgsigqueueinfo = 322,
+ fanotify_init = 323,
+ fanotify_mark = 324,
+ prlimit64 = 325,
+ socket = 326,
+ bind = 327,
+ connect = 328,
+ listen = 329,
+ accept = 330,
+ getsockname = 331,
+ getpeername = 332,
+ socketpair = 333,
+ send = 334,
+ sendto = 335,
+ recv = 336,
+ recvfrom = 337,
+ shutdown = 338,
+ setsockopt = 339,
+ getsockopt = 340,
+ sendmsg = 341,
+ recvmsg = 342,
+ recvmmsg = 343,
+ accept4 = 344,
+ name_to_handle_at = 345,
+ open_by_handle_at = 346,
+ clock_adjtime = 347,
+ syncfs = 348,
+ sendmmsg = 349,
+ setns = 350,
+ process_vm_readv = 351,
+ process_vm_writev = 352,
+ finit_module = 353,
+ kcmp = 354,
+ sched_setattr = 355,
+ sched_getattr = 356,
+ renameat2 = 357,
+ seccomp = 358,
+ getrandom = 359,
+ memfd_create = 360,
+ bpf = 361,
+ execveat = 362,
+ switch_endian = 363,
+ userfaultfd = 364,
+ membarrier = 365,
+ mlock2 = 378,
+ copy_file_range = 379,
+ preadv2 = 380,
+ pwritev2 = 381,
+ kexec_file_load = 382,
+ statx = 383,
+ pkey_alloc = 384,
+ pkey_free = 385,
+ pkey_mprotect = 386,
+ rseq = 387,
+ io_pgetevents = 388,
+ semget = 393,
+ semctl = 394,
+ shmget = 395,
+ shmctl = 396,
+ shmat = 397,
+ shmdt = 398,
+ msgget = 399,
+ msgsnd = 400,
+ msgrcv = 401,
+ msgctl = 402,
+ clock_gettime64 = 403,
+ clock_settime64 = 404,
+ clock_adjtime64 = 405,
+ clock_getres_time64 = 406,
+ clock_nanosleep_time64 = 407,
+ timer_gettime64 = 408,
+ timer_settime64 = 409,
+ timerfd_gettime64 = 410,
+ timerfd_settime64 = 411,
+ utimensat_time64 = 412,
+ pselect6_time64 = 413,
+ ppoll_time64 = 414,
+ io_pgetevents_time64 = 416,
+ recvmmsg_time64 = 417,
+ mq_timedsend_time64 = 418,
+ mq_timedreceive_time64 = 419,
+ semtimedop_time64 = 420,
+ rt_sigtimedwait_time64 = 421,
+ futex_time64 = 422,
+ sched_rr_get_interval_time64 = 423,
+ pidfd_send_signal = 424,
+ io_uring_setup = 425,
+ io_uring_enter = 426,
+ io_uring_register = 427,
+ open_tree = 428,
+ move_mount = 429,
+ fsopen = 430,
+ fsconfig = 431,
+ fsmount = 432,
+ fspick = 433,
+ pidfd_open = 434,
+ clone3 = 435,
+ close_range = 436,
+ openat2 = 437,
+ pidfd_getfd = 438,
+ faccessat2 = 439,
+ process_madvise = 440,
+};
+
+pub const O_CREAT = 0o100;
+pub const O_EXCL = 0o200;
+pub const O_NOCTTY = 0o400;
+pub const O_TRUNC = 0o1000;
+pub const O_APPEND = 0o2000;
+pub const O_NONBLOCK = 0o4000;
+pub const O_DSYNC = 0o10000;
+pub const O_SYNC = 0o4010000;
+pub const O_RSYNC = 0o4010000;
+pub const O_DIRECTORY = 0o40000;
+pub const O_NOFOLLOW = 0o100000;
+pub const O_CLOEXEC = 0o2000000;
+
+pub const O_ASYNC = 0o20000;
+pub const O_DIRECT = 0o400000;
+pub const O_LARGEFILE = 0o200000;
+pub const O_NOATIME = 0o1000000;
+pub const O_PATH = 0o10000000;
+pub const O_TMPFILE = 0o20040000;
+pub const O_NDELAY = O_NONBLOCK;
+
+pub const F_DUPFD = 0;
+pub const F_GETFD = 1;
+pub const F_SETFD = 2;
+pub const F_GETFL = 3;
+pub const F_SETFL = 4;
+
+pub const F_SETOWN = 8;
+pub const F_GETOWN = 9;
+pub const F_SETSIG = 10;
+pub const F_GETSIG = 11;
+
+pub const F_GETLK = 12;
+pub const F_SETLK = 13;
+pub const F_SETLKW = 14;
+
+pub const F_SETOWN_EX = 15;
+pub const F_GETOWN_EX = 16;
+
+pub const F_GETOWNER_UIDS = 17;
+
+pub const F_RDLCK = 0;
+pub const F_WRLCK = 1;
+pub const F_UNLCK = 2;
+
+pub const LOCK_SH = 1;
+pub const LOCK_EX = 2;
+pub const LOCK_UN = 8;
+pub const LOCK_NB = 4;
+
+/// stack-like segment
+pub const MAP_GROWSDOWN = 0x0100;
+
+/// ETXTBSY
+pub const MAP_DENYWRITE = 0x0800;
+
+/// mark it as an executable
+pub const MAP_EXECUTABLE = 0x1000;
+
+/// pages are locked
+pub const MAP_LOCKED = 0x0080;
+
+/// don't check for reservations
+pub const MAP_NORESERVE = 0x0040;
+
+pub const VDSO_CGT_SYM = "__kernel_clock_gettime";
+pub const VDSO_CGT_VER = "LINUX_2.6.15";
+
+pub const Flock = extern struct {
+ l_type: i16,
+ l_whence: i16,
+ l_start: off_t,
+ l_len: off_t,
+ l_pid: pid_t,
+};
+
+pub const msghdr = extern struct {
+ msg_name: ?*sockaddr,
+ msg_namelen: socklen_t,
+ msg_iov: [*]iovec,
+ msg_iovlen: usize,
+ msg_control: ?*c_void,
+ msg_controllen: socklen_t,
+ msg_flags: i32,
+};
+
+pub const msghdr_const = extern struct {
+ msg_name: ?*const sockaddr,
+ msg_namelen: socklen_t,
+ msg_iov: [*]iovec_const,
+ msg_iovlen: usize,
+ msg_control: ?*c_void,
+ msg_controllen: socklen_t,
+ msg_flags: i32,
+};
+
+pub const blksize_t = i32;
+pub const nlink_t = u32;
+pub const time_t = isize;
+pub const mode_t = u32;
+pub const off_t = i64;
+pub const ino_t = u64;
+pub const dev_t = u64;
+pub const blkcnt_t = i64;
+
+// The `stat` definition used by the Linux kernel.
+pub const kernel_stat = extern struct {
+ dev: dev_t,
+ ino: ino_t,
+ mode: mode_t,
+ nlink: nlink_t,
+ uid: uid_t,
+ gid: gid_t,
+ rdev: dev_t,
+ __rdev_padding: i16,
+ size: off_t,
+ blksize: blksize_t,
+ blocks: blkcnt_t,
+ __atim32: timespec32,
+ __mtim32: timespec32,
+ __ctim32: timespec32,
+ __unused: [2]u32,
+ atim: timespec,
+ mtim: timespec,
+ ctim: timespec,
+
+ const timespec32 = extern struct {
+ tv_sec: i32,
+ tv_nsec: i32,
+ };
+
+ pub fn atime(self: @This()) timespec {
+ return self.atim;
+ }
+
+ pub fn mtime(self: @This()) timespec {
+ return self.mtim;
+ }
+
+ pub fn ctime(self: @This()) timespec {
+ return self.ctim;
+ }
+};
+
+// The `stat64` definition used by the libc.
+pub const libc_stat = kernel_stat;
+
+pub const timespec = extern struct {
+ tv_sec: time_t,
+ tv_nsec: isize,
+};
+
+pub const timeval = extern struct {
+ tv_sec: time_t,
+ tv_usec: isize,
+};
+
+pub const timezone = extern struct {
+ tz_minuteswest: i32,
+ tz_dsttime: i32,
+};
+
+pub const greg_t = u32;
+pub const gregset_t = [48]greg_t;
+pub const fpregset_t = [33]f64;
+
+pub const vrregset = extern struct {
+ vrregs: [32][4]u32,
+ vrsave: u32,
+ _pad: [2]u32,
+ vscr: u32,
+};
+pub const vrregset_t = vrregset;
+
+pub const mcontext_t = extern struct {
+ gp_regs: gregset_t,
+ fp_regs: fpregset_t,
+ v_regs: vrregset_t align(16),
+};
+
+pub const ucontext_t = extern struct {
+ flags: u32,
+ link: *ucontext_t,
+ stack: stack_t,
+ pad: [7]i32,
+ regs: *mcontext_t,
+ sigmask: sigset_t,
+ pad2: [3]i32,
+ mcontext: mcontext_t,
+};
+
+pub const Elf_Symndx = u32;
+
+pub const MMAP2_UNIT = 4096;
diff --git a/lib/std/os/bits/netbsd.zig b/lib/std/os/bits/netbsd.zig
@@ -813,10 +813,6 @@ pub const sigset_t = extern struct {
__bits: [_SIG_WORDS]u32,
};
-pub const SIG_ERR = @intToPtr(?Sigaction.sigaction_fn, maxInt(usize));
-pub const SIG_DFL = @intToPtr(?Sigaction.sigaction_fn, 0);
-pub const SIG_IGN = @intToPtr(?Sigaction.sigaction_fn, 1);
-
pub const empty_sigset = sigset_t{ .__bits = [_]u32{0} ** _SIG_WORDS };
// XXX x86_64 specific
@@ -1219,3 +1215,25 @@ pub const rlimit = extern struct {
pub const SHUT_RD = 0;
pub const SHUT_WR = 1;
pub const SHUT_RDWR = 2;
+
+pub const nfds_t = u32;
+
+pub const pollfd = extern struct {
+ fd: fd_t,
+ events: i16,
+ revents: i16,
+};
+
+/// Testable events (may be specified in events field).
+pub const POLLIN = 0x0001;
+pub const POLLPRI = 0x0002;
+pub const POLLOUT = 0x0004;
+pub const POLLRDNORM = 0x0040;
+pub const POLLWRNORM = POLLOUT;
+pub const POLLRDBAND = 0x0080;
+pub const POLLWRBAND = 0x0100;
+
+/// Non-testable events (may not be specified in events field).
+pub const POLLERR = 0x0008;
+pub const POLLHUP = 0x0010;
+pub const POLLNVAL = 0x0020;
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
@@ -26,6 +26,7 @@ pub usingnamespace switch (builtin.arch) {
.riscv64 => @import("linux/riscv64.zig"),
.sparcv9 => @import("linux/sparc64.zig"),
.mips, .mipsel => @import("linux/mips.zig"),
+ .powerpc => @import("linux/powerpc.zig"),
.powerpc64, .powerpc64le => @import("linux/powerpc64.zig"),
else => struct {},
};
diff --git a/lib/std/os/linux/io_uring.zig b/lib/std/os/linux/io_uring.zig
@@ -1354,7 +1354,7 @@ test "timeout (after a relative time)" {
.flags = 0,
}, cqe);
- // Tests should not depend on timings: skip test (result) if outside margin.
+ // Tests should not depend on timings: skip test if outside margin.
if (!std.math.approxEqAbs(f64, ms, @intToFloat(f64, stopped - started), margin)) return error.SkipZigTest;
}
diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2015-2021 Zig Contributors
+// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
+// The MIT license requires this copyright notice to be included in all copies
+// and substantial portions of the software.
+
+usingnamespace @import("../bits.zig");
+
+pub fn syscall0(number: SYS) usize {
+ return asm volatile (
+ \\ sc
+ \\ bns+ 1f
+ \\ neg 3, 3
+ \\ 1:
+ : [ret] "={r3}" (-> usize)
+ : [number] "{r0}" (@enumToInt(number))
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
+
+pub fn syscall1(number: SYS, arg1: usize) usize {
+ return asm volatile (
+ \\ sc
+ \\ bns+ 1f
+ \\ neg 3, 3
+ \\ 1:
+ : [ret] "={r3}" (-> usize)
+ : [number] "{r0}" (@enumToInt(number)),
+ [arg1] "{r3}" (arg1)
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
+
+pub fn syscall2(number: SYS, arg1: usize, arg2: usize) usize {
+ return asm volatile (
+ \\ sc
+ \\ bns+ 1f
+ \\ neg 3, 3
+ \\ 1:
+ : [ret] "={r3}" (-> usize)
+ : [number] "{r0}" (@enumToInt(number)),
+ [arg1] "{r3}" (arg1),
+ [arg2] "{r4}" (arg2)
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
+
+pub fn syscall3(number: SYS, arg1: usize, arg2: usize, arg3: usize) usize {
+ return asm volatile (
+ \\ sc
+ \\ bns+ 1f
+ \\ neg 3, 3
+ \\ 1:
+ : [ret] "={r3}" (-> usize)
+ : [number] "{r0}" (@enumToInt(number)),
+ [arg1] "{r3}" (arg1),
+ [arg2] "{r4}" (arg2),
+ [arg3] "{r5}" (arg3)
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
+
+pub fn syscall4(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize) usize {
+ return asm volatile (
+ \\ sc
+ \\ bns+ 1f
+ \\ neg 3, 3
+ \\ 1:
+ : [ret] "={r3}" (-> usize)
+ : [number] "{r0}" (@enumToInt(number)),
+ [arg1] "{r3}" (arg1),
+ [arg2] "{r4}" (arg2),
+ [arg3] "{r5}" (arg3),
+ [arg4] "{r6}" (arg4)
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
+
+pub fn syscall5(number: SYS, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) usize {
+ return asm volatile (
+ \\ sc
+ \\ bns+ 1f
+ \\ neg 3, 3
+ \\ 1:
+ : [ret] "={r3}" (-> usize)
+ : [number] "{r0}" (@enumToInt(number)),
+ [arg1] "{r3}" (arg1),
+ [arg2] "{r4}" (arg2),
+ [arg3] "{r5}" (arg3),
+ [arg4] "{r6}" (arg4),
+ [arg5] "{r7}" (arg5)
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
+
+pub fn syscall6(
+ number: SYS,
+ arg1: usize,
+ arg2: usize,
+ arg3: usize,
+ arg4: usize,
+ arg5: usize,
+ arg6: usize,
+) usize {
+ return asm volatile (
+ \\ sc
+ \\ bns+ 1f
+ \\ neg 3, 3
+ \\ 1:
+ : [ret] "={r3}" (-> usize)
+ : [number] "{r0}" (@enumToInt(number)),
+ [arg1] "{r3}" (arg1),
+ [arg2] "{r4}" (arg2),
+ [arg3] "{r5}" (arg3),
+ [arg4] "{r6}" (arg4),
+ [arg5] "{r7}" (arg5),
+ [arg6] "{r8}" (arg6)
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
+
+/// This matches the libc clone function.
+pub extern fn clone(func: fn (arg: usize) callconv(.C) u8, stack: usize, flags: usize, arg: usize, ptid: *i32, tls: usize, ctid: *i32) usize;
+
+pub const restore = restore_rt;
+
+pub fn restore_rt() callconv(.Naked) void {
+ return asm volatile ("sc"
+ :
+ : [number] "{r0}" (@enumToInt(SYS.rt_sigreturn))
+ : "memory", "cr0", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12"
+ );
+}
diff --git a/lib/std/special/c.zig b/lib/std/special/c.zig
@@ -491,6 +491,71 @@ fn clone() callconv(.Naked) void {
\\ syscall
);
},
+ .powerpc => {
+ // __clone(func, stack, flags, arg, ptid, tls, ctid)
+ // 3, 4, 5, 6, 7, 8, 9
+
+ // syscall(SYS_clone, flags, stack, ptid, tls, ctid)
+ // 0 3, 4, 5, 6, 7
+ asm volatile (
+ \\# store non-volatile regs r30, r31 on stack in order to put our
+ \\# start func and its arg there
+ \\stwu 30, -16(1)
+ \\stw 31, 4(1)
+ \\
+ \\# save r3 (func) into r30, and r6(arg) into r31
+ \\mr 30, 3
+ \\mr 31, 6
+ \\
+ \\# create initial stack frame for new thread
+ \\clrrwi 4, 4, 4
+ \\li 0, 0
+ \\stwu 0, -16(4)
+ \\
+ \\#move c into first arg
+ \\mr 3, 5
+ \\#mr 4, 4
+ \\mr 5, 7
+ \\mr 6, 8
+ \\mr 7, 9
+ \\
+ \\# move syscall number into r0
+ \\li 0, 120
+ \\
+ \\sc
+ \\
+ \\# check for syscall error
+ \\bns+ 1f # jump to label 1 if no summary overflow.
+ \\#else
+ \\neg 3, 3 #negate the result (errno)
+ \\1:
+ \\# compare sc result with 0
+ \\cmpwi cr7, 3, 0
+ \\
+ \\# if not 0, jump to end
+ \\bne cr7, 2f
+ \\
+ \\#else: we're the child
+ \\#call funcptr: move arg (d) into r3
+ \\mr 3, 31
+ \\#move r30 (funcptr) into CTR reg
+ \\mtctr 30
+ \\# call CTR reg
+ \\bctrl
+ \\# mov SYS_exit into r0 (the exit param is already in r3)
+ \\li 0, 1
+ \\sc
+ \\
+ \\2:
+ \\
+ \\# restore stack
+ \\lwz 30, 0(1)
+ \\lwz 31, 4(1)
+ \\addi 1, 1, 16
+ \\
+ \\blr
+ );
+ },
.powerpc64, .powerpc64le => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// 3, 4, 5, 6, 7, 8, 9
diff --git a/lib/std/special/docs/index.html b/lib/std/special/docs/index.html
@@ -43,6 +43,8 @@
/* layout */
.canvas {
+ display: flex;
+ flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
@@ -53,12 +55,21 @@
background-color: var(--bg-color);
}
+ .banner {
+ background-color: darkred;
+ text-align: center;
+ color: white;
+ padding: 15px 5px;
+ }
+
+ .banner a {
+ color: bisque;
+ text-decoration: underline;
+ }
+
.flex-main {
display: flex;
- width: 100%;
- height: 100%;
- justify-content: center;
-
+ overflow-y: hidden;
z-index: 100;
}
@@ -515,7 +526,7 @@
</style>
</head>
<body class="canvas">
- <div style="background-color: darkred; width: 100vw; text-align: center; color: white; padding: 15px 5px;">These docs are experimental. <a style="color: bisque;text-decoration: underline;" href="https://kristoff.it/blog/zig-new-relationship-llvm/">Progress depends on the self-hosted compiler</a>, <a style="color: bisque;text-decoration: underline;" href="https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code">consider reading the stlib source in the meantime</a>.</div>
+ <div class="banner">These docs are experimental. <a href="https://kristoff.it/blog/zig-new-relationship-llvm/">Progress depends on the self-hosted compiler</a>, <a href="https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code">consider reading the stdlib source in the meantime</a>.</div>
<div class="flex-main">
<div class="flex-filler"></div>
<div class="flex-left sidebar">
diff --git a/lib/std/special/docs/main.js b/lib/std/special/docs/main.js
@@ -1844,7 +1844,13 @@
var oldHash = location.hash;
var parts = oldHash.split("?");
var newPart2 = (domSearch.value === "") ? "" : ("?" + domSearch.value);
- location.hash = (parts.length === 1) ? (oldHash + newPart2) : (parts[0] + newPart2);
+ var newHash = (oldHash === "" ? "#" : parts[0]) + newPart2;
+ // create a history entry only once per search
+ if (parts.length === 1) {
+ location.assign(newHash);
+ } else {
+ location.replace(newHash);
+ }
}
function getSearchTerms() {
var list = curNavSearch.trim().split(/[ \r\n\t]+/);
diff --git a/lib/std/start.zig b/lib/std/start.zig
@@ -7,7 +7,7 @@
const root = @import("root");
const std = @import("std.zig");
-const builtin = std.builtin;
+const builtin = @import("builtin");
const assert = std.debug.assert;
const uefi = std.os.uefi;
const tlcsprng = @import("crypto/tlcsprng.zig");
@@ -17,39 +17,101 @@ var argc_argv_ptr: [*]usize = undefined;
const start_sym_name = if (builtin.arch.isMIPS()) "__start" else "_start";
comptime {
- if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) {
- if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
- @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
+ // The self-hosted compiler is not fully capable of handling all of this start.zig file.
+ // Until then, we have simplified logic here for self-hosted. TODO remove this once
+ // self-hosted is capable enough to handle all of the real start.zig logic.
+ if (builtin.zig_is_stage2) {
+ if (builtin.output_mode == .Exe) {
+ if (builtin.link_libc or builtin.object_format == .c) {
+ if (!@hasDecl(root, "main")) {
+ @export(main2, "main");
+ }
+ } else {
+ if (!@hasDecl(root, "_start")) {
+ @export(_start2, "_start");
+ }
+ }
}
- } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
- if (builtin.link_libc and @hasDecl(root, "main")) {
- if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
- @export(main, .{ .name = "main", .linkage = .Weak });
+ } else {
+ if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) {
+ if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
+ @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
}
- } else if (builtin.os.tag == .windows) {
- if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
- !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
- {
- @export(WinStartup, .{ .name = "wWinMainCRTStartup" });
- } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
- !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
- {
- @compileError("WinMain not supported; declare wWinMain or main instead");
- } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
- !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
- {
- @export(wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
+ } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
+ if (builtin.link_libc and @hasDecl(root, "main")) {
+ if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
+ @export(main, .{ .name = "main", .linkage = .Weak });
+ }
+ } else if (builtin.os.tag == .windows) {
+ if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
+ !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
+ {
+ @export(WinStartup, .{ .name = "wWinMainCRTStartup" });
+ } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
+ !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
+ {
+ @compileError("WinMain not supported; declare wWinMain or main instead");
+ } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
+ !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
+ {
+ @export(wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
+ }
+ } else if (builtin.os.tag == .uefi) {
+ if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" });
+ } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) {
+ if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name });
+ } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) {
+ if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
}
- } else if (builtin.os.tag == .uefi) {
- if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" });
- } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) {
- if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name });
- } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) {
- if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
}
}
}
+// Simplified start code for stage2 until it supports more language features ///
+
+fn main2() callconv(.C) c_int {
+ root.main();
+ return 0;
+}
+
+fn _start2() callconv(.Naked) noreturn {
+ root.main();
+ exit2(0);
+}
+
+fn exit2(code: u8) noreturn {
+ switch (builtin.arch) {
+ .x86_64 => {
+ asm volatile ("syscall"
+ :
+ : [number] "{rax}" (231),
+ [arg1] "{rdi}" (code)
+ : "rcx", "r11", "memory"
+ );
+ },
+ .arm => {
+ asm volatile ("svc #0"
+ :
+ : [number] "{r7}" (1),
+ [arg1] "{r0}" (code)
+ : "memory"
+ );
+ },
+ .aarch64 => {
+ asm volatile ("svc #0"
+ :
+ : [number] "{x8}" (93),
+ [arg1] "{x0}" (code)
+ : "memory", "cc"
+ );
+ },
+ else => @compileError("TODO"),
+ }
+ unreachable;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
fn _DllMainCRTStartup(
hinstDLL: std.os.windows.HINSTANCE,
fdwReason: std.os.windows.DWORD,
@@ -135,6 +197,19 @@ fn _start() callconv(.Naked) noreturn {
: [argc] "={sp}" (-> [*]usize)
);
},
+ .powerpc => {
+ // Setup the initial stack frame and clear the back chain pointer.
+ argc_argv_ptr = asm volatile (
+ \\ mr 4, 1
+ \\ li 0, 0
+ \\ stwu 1,-16(1)
+ \\ stw 0, 0(1)
+ \\ mtlr 0
+ : [argc] "={r4}" (-> [*]usize)
+ :
+ : "r0"
+ );
+ },
.powerpc64le => {
// Setup the initial stack frame and clear the back chain pointer.
// TODO: Support powerpc64 (big endian) on ELFv2.
diff --git a/lib/std/std.zig b/lib/std/std.zig
@@ -92,7 +92,7 @@ pub const zig = @import("zig.zig");
pub const start = @import("start.zig");
// This forces the start.zig file to be imported, and the comptime logic inside that
-// file decides whether to export any appropriate start symbols.
+// file decides whether to export any appropriate start symbols, and call main.
comptime {
_ = start;
}
diff --git a/lib/std/target.zig b/lib/std/target.zig
@@ -1178,7 +1178,7 @@ pub const Target = struct {
return switch (arch) {
.arm, .armeb, .thumb, .thumbeb => &arm.cpu.generic,
.aarch64, .aarch64_be, .aarch64_32 => &aarch64.cpu.generic,
- .avr => &avr.cpu.avr1,
+ .avr => &avr.cpu.avr2,
.bpfel, .bpfeb => &bpf.cpu.generic,
.hexagon => &hexagon.cpu.generic,
.mips, .mipsel => &mips.cpu.mips32,
diff --git a/lib/std/testing.zig b/lib/std/testing.zig
@@ -431,7 +431,7 @@ fn printIndicatorLine(source: []const u8, indicator_index: usize) void {
fn printWithVisibleNewlines(source: []const u8) void {
var i: usize = 0;
- while (std.mem.indexOf(u8, source[i..], "\n")) |nl| : (i += nl + 1) {
+ while (std.mem.indexOfScalar(u8, source[i..], '\n')) |nl| : (i += nl + 1) {
printLine(source[i .. i + nl]);
}
print("{s}␃\n", .{source[i..]}); // End of Text symbol (ETX)
@@ -439,7 +439,7 @@ fn printWithVisibleNewlines(source: []const u8) void {
fn printLine(line: []const u8) void {
if (line.len != 0) switch (line[line.len - 1]) {
- ' ', '\t' => print("{s}⏎\n", .{line}), // Carriage return symbol,
+ ' ', '\t' => return print("{s}⏎\n", .{line}), // Carriage return symbol,
else => {},
};
print("{s}\n", .{line});
@@ -451,7 +451,7 @@ test {
/// Given a type, reference all the declarations inside, so that the semantic analyzer sees them.
pub fn refAllDecls(comptime T: type) void {
- if (!@import("builtin").is_test) return;
+ if (!std.builtin.is_test) return;
inline for (std.meta.declarations(T)) |decl| {
_ = decl;
}
diff --git a/lib/std/time.zig b/lib/std/time.zig
@@ -271,7 +271,9 @@ test "timestamp" {
sleep(ns_per_ms);
const time_1 = milliTimestamp();
const interval = time_1 - time_0;
- testing.expect(interval > 0 and interval < margin);
+ testing.expect(interval > 0);
+ // Tests should not depend on timings: skip test if outside margin.
+ if (!(interval < margin)) return error.SkipZigTest;
}
test "Timer" {
@@ -280,7 +282,9 @@ test "Timer" {
var timer = try Timer.start();
sleep(10 * ns_per_ms);
const time_0 = timer.read();
- testing.expect(time_0 > 0 and time_0 < margin);
+ testing.expect(time_0 > 0);
+ // Tests should not depend on timings: skip test if outside margin.
+ if (!(time_0 < margin)) return error.SkipZigTest;
const time_1 = timer.lap();
testing.expect(time_1 >= time_0);
diff --git a/lib/std/wasm.zig b/lib/std/wasm.zig
@@ -5,6 +5,8 @@
// and substantial portions of the software.
const testing = @import("std.zig").testing;
+// TODO: Add support for multi-byte ops (e.g. table operations)
+
/// Wasm instruction opcodes
///
/// All instructions are defined as per spec:
@@ -175,7 +177,7 @@ pub const Opcode = enum(u8) {
i32_reinterpret_f32 = 0xBC,
i64_reinterpret_f64 = 0xBD,
f32_reinterpret_i32 = 0xBE,
- i64_reinterpret_i64 = 0xBF,
+ f64_reinterpret_i64 = 0xBF,
i32_extend8_s = 0xC0,
i32_extend16_s = 0xC1,
i64_extend8_s = 0xC2,
@@ -278,3 +280,6 @@ pub const block_empty: u8 = 0x40;
// binary constants
pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1
+
+// Each wasm page size is 64kB
+pub const page_size = 64 * 1024;
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
@@ -18,34 +18,54 @@ pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;
pub const SrcHash = [16]u8;
-/// If the source is small enough, it is used directly as the hash.
-/// If it is long, blake3 hash is computed.
pub fn hashSrc(src: []const u8) SrcHash {
var out: SrcHash = undefined;
- if (src.len <= @typeInfo(SrcHash).Array.len) {
- std.mem.copy(u8, &out, src);
- std.mem.set(u8, out[src.len..], 0);
- } else {
- std.crypto.hash.Blake3.hash(src, &out, .{});
- }
+ std.crypto.hash.Blake3.hash(src, &out, .{});
return out;
}
-pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } {
+pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash {
+ var out: SrcHash = undefined;
+ var hasher = std.crypto.hash.Blake3.init(.{});
+ hasher.update(&parent_hash);
+ hasher.update(sep);
+ hasher.update(name);
+ hasher.final(&out);
+ return out;
+}
+
+pub const Loc = struct {
+ line: usize,
+ column: usize,
+ /// Does not include the trailing newline.
+ source_line: []const u8,
+};
+
+pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc {
var line: usize = 0;
var column: usize = 0;
- for (source[0..byte_offset]) |byte| {
- switch (byte) {
+ var line_start: usize = 0;
+ var i: usize = 0;
+ while (i < byte_offset) : (i += 1) {
+ switch (source[i]) {
'\n' => {
line += 1;
column = 0;
+ line_start = i + 1;
},
else => {
column += 1;
},
}
}
- return .{ .line = line, .column = column };
+ while (i < source.len and source[i] != '\n') {
+ i += 1;
+ }
+ return .{
+ .line = line,
+ .column = column,
+ .source_line = source[line_start..i],
+ };
}
pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
@@ -1801,17 +1801,21 @@ test "zig fmt: array literal with hint" {
);
}
-test "zig fmt: array literal veritical column alignment" {
+test "zig fmt: array literal vertical column alignment" {
try testTransform(
\\const a = []u8{
\\ 1000, 200,
\\ 30, 4,
- \\ 50000, 60
+ \\ 50000, 60,
\\};
\\const a = []u8{0, 1, 2, 3, 40,
\\ 4,5,600,7,
\\ 80,
- \\ 9, 10, 11, 0, 13, 14, 15};
+ \\ 9, 10, 11, 0, 13, 14, 15,};
+ \\const a = [12]u8{
+ \\ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ \\const a = [12]u8{
+ \\ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, };
\\
,
\\const a = []u8{
@@ -1825,6 +1829,21 @@ test "zig fmt: array literal veritical column alignment" {
\\ 9, 10, 11, 0, 13,
\\ 14, 15,
\\};
+ \\const a = [12]u8{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ \\const a = [12]u8{
+ \\ 31,
+ \\ 28,
+ \\ 31,
+ \\ 30,
+ \\ 31,
+ \\ 30,
+ \\ 31,
+ \\ 31,
+ \\ 30,
+ \\ 31,
+ \\ 30,
+ \\ 31,
+ \\};
\\
);
}
@@ -2026,10 +2045,7 @@ test "zig fmt: add trailing comma to array literal" {
\\ return []u16{
\\ 'm', 's', 'y', 's', '-', // hi
\\ };
- \\ return []u16{
- \\ 'm', 's', 'y', 's',
- \\ '-',
- \\ };
+ \\ return []u16{ 'm', 's', 'y', 's', '-' };
\\ return []u16{ 'm', 's', 'y', 's', '-' };
\\}
\\
@@ -4661,6 +4677,90 @@ test "zig fmt: insert trailing comma if there are comments between switch values
);
}
+test "zig fmt: insert trailing comma if comments in array init" {
+ try testTransform(
+ \\var a = .{
+ \\ "foo", //
+ \\ "bar"
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "bar" //
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "//"
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "//" //
+ \\};
+ \\
+ ,
+ \\var a = .{
+ \\ "foo", //
+ \\ "bar",
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "bar", //
+ \\};
+ \\var a = .{ "foo", "//" };
+ \\var a = .{
+ \\ "foo",
+ \\ "//", //
+ \\};
+ \\
+ );
+}
+
+test "zig fmt: make single-line if no trailing comma" {
+ try testTransform(
+ \\test "function call no trailing comma" {
+ \\ foo(
+ \\ 1,
+ \\ 2
+ \\ );
+ \\}
+ \\
+ ,
+ \\test "function call no trailing comma" {
+ \\ foo(1, 2);
+ \\}
+ \\
+ );
+
+ try testTransform(
+ \\test "struct no trailing comma" {
+ \\ const a = .{
+ \\ .foo = 1,
+ \\ .bar = 2
+ \\ };
+ \\}
+ \\
+ ,
+ \\test "struct no trailing comma" {
+ \\ const a = .{ .foo = 1, .bar = 2 };
+ \\}
+ \\
+ );
+
+ try testTransform(
+ \\test "array no trailing comma" {
+ \\ var stream = multiOutStream(.{
+ \\ fbs1.outStream(),
+ \\ fbs2.outStream()
+ \\ });
+ \\}
+ \\
+ ,
+ \\test "array no trailing comma" {
+ \\ var stream = multiOutStream(.{ fbs1.outStream(), fbs2.outStream() });
+ \\}
+ \\
+ );
+}
+
test "zig fmt: error for invalid bit range" {
try testError(
\\var x: []align(0:0:0)u8 = bar;
diff --git a/lib/std/zig/perf_test.zig b/lib/std/zig/perf_test.zig
@@ -9,6 +9,7 @@ const warn = std.debug.warn;
const Tokenizer = std.zig.Tokenizer;
const Parser = std.zig.Parser;
const io = std.io;
+const fmtIntSizeBin = std.fmt.fmtIntSizeBin;
const source = @embedFile("../os.zig");
var fixed_buffer_mem: [10 * 1024 * 1024]u8 = undefined;
@@ -25,12 +26,15 @@ pub fn main() !void {
const end = timer.read();
memory_used /= iterations;
const elapsed_s = @intToFloat(f64, end - start) / std.time.ns_per_s;
- const bytes_per_sec = @intToFloat(f64, source.len * iterations) / elapsed_s;
- const mb_per_sec = bytes_per_sec / (1024 * 1024);
+ const bytes_per_sec_float = @intToFloat(f64, source.len * iterations) / elapsed_s;
+ const bytes_per_sec = @floatToInt(u64, @floor(bytes_per_sec_float));
var stdout_file = std.io.getStdOut();
const stdout = stdout_file.writer();
- try stdout.print("{:.3} MiB/s, {} KiB used \n", .{ mb_per_sec, memory_used / 1024 });
+ try stdout.print("parsing speed: {:.2}/s, {:.2} used \n", .{
+ fmtIntSizeBin(bytes_per_sec),
+ fmtIntSizeBin(memory_used),
+ });
}
fn testOnce() usize {
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
@@ -1632,9 +1632,10 @@ fn renderArrayInit(
}
}
- const contains_newlines = !tree.tokensOnSameLine(array_init.ast.lbrace, rbrace);
+ const contains_comment = hasComment(tree, array_init.ast.lbrace, rbrace);
+ const contains_multiline_string = hasMultilineString(tree, array_init.ast.lbrace, rbrace);
- if (!trailing_comma and !contains_newlines) {
+ if (!trailing_comma and !contains_comment and !contains_multiline_string) {
// Render all on one line, no trailing comma.
if (array_init.ast.elements.len == 1) {
// If there is only one element, we don't use spaces
@@ -1653,7 +1654,8 @@ fn renderArrayInit(
try renderToken(ais, tree, array_init.ast.lbrace, .newline);
var expr_index: usize = 0;
- while (rowSize(tree, array_init.ast.elements[expr_index..], rbrace)) |row_size| {
+ while (true) {
+ const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
const row_exprs = array_init.ast.elements[expr_index..];
// A place to store the width of each expression and its column's maximum
const widths = try gpa.alloc(usize, row_exprs.len + row_size);
@@ -1686,7 +1688,7 @@ fn renderArrayInit(
const maybe_comma = expr_last_token + 1;
if (token_tags[maybe_comma] == .comma) {
if (hasSameLineComment(tree, maybe_comma))
- break :sec_end i - this_line_size.? + 1;
+ break :sec_end i - this_line_size + 1;
}
}
break :sec_end row_exprs.len;
@@ -2238,17 +2240,36 @@ fn renderToken(ais: *Ais, tree: ast.Tree, token_index: ast.TokenIndex, space: Sp
}
}
-/// Returns true if there exists a comment between the start of token
-/// `start_token` and the start of token `end_token`. This is used to determine
-/// if e.g. a fn_proto should be wrapped and have a trailing comma inserted
-/// even if there is none in the source.
+/// Returns true if there exists a comment between any of the tokens from
+/// `start_token` to `end_token`. This is used to determine if e.g. a
+/// fn_proto should be wrapped and have a trailing comma inserted even if
+/// there is none in the source.
fn hasComment(tree: ast.Tree, start_token: ast.TokenIndex, end_token: ast.TokenIndex) bool {
const token_starts = tree.tokens.items(.start);
- const start = token_starts[start_token];
- const end = token_starts[end_token];
+ var i = start_token;
+ while (i < end_token) : (i += 1) {
+ const start = token_starts[i] + tree.tokenSlice(i).len;
+ const end = token_starts[i + 1];
+ if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
+ }
+
+ return false;
+}
- return mem.indexOf(u8, tree.source[start..end], "//") != null;
+/// Returns true if there exists a multiline string literal between the start
+/// of token `start_token` and the start of token `end_token`.
+fn hasMultilineString(tree: ast.Tree, start_token: ast.TokenIndex, end_token: ast.TokenIndex) bool {
+ const token_tags = tree.tokens.items(.tag);
+
+ for (token_tags[start_token..end_token]) |tag| {
+ switch (tag) {
+ .multiline_string_literal_line => return true,
+ else => continue,
+ }
+ }
+
+ return false;
}
/// Assumes that start is the first byte past the previous token and
@@ -2500,9 +2521,8 @@ fn nodeCausesSliceOpSpace(tag: ast.Node.Tag) bool {
};
}
-// Returns the number of nodes in `expr` that are on the same line as `rtoken`,
-// or null if they all are on the same line.
-fn rowSize(tree: ast.Tree, exprs: []const ast.Node.Index, rtoken: ast.TokenIndex) ?usize {
+// Returns the number of nodes in `expr` that are on the same line as `rtoken`.
+fn rowSize(tree: ast.Tree, exprs: []const ast.Node.Index, rtoken: ast.TokenIndex) usize {
const token_tags = tree.tokens.items(.tag);
const first_token = tree.firstToken(exprs[0]);
@@ -2510,7 +2530,7 @@ fn rowSize(tree: ast.Tree, exprs: []const ast.Node.Index, rtoken: ast.TokenIndex
const maybe_comma = rtoken - 1;
if (token_tags[maybe_comma] == .comma)
return 1;
- return null; // no newlines
+ return exprs.len; // no newlines
}
var count: usize = 1;
diff --git a/src/AstGen.zig b/src/AstGen.zig
@@ -28,8 +28,6 @@ const BuiltinFn = @import("BuiltinFn.zig");
instructions: std.MultiArrayList(zir.Inst) = .{},
string_bytes: ArrayListUnmanaged(u8) = .{},
extra: ArrayListUnmanaged(u32) = .{},
-decl_map: std.StringArrayHashMapUnmanaged(void) = .{},
-decls: ArrayListUnmanaged(*Decl) = .{},
/// The end of special indexes. `zir.Inst.Ref` subtracts against this number to convert
/// to `zir.Inst.Index`. The default here is correct if there are 0 parameters.
ref_start_index: u32 = zir.Inst.Ref.typed_value_map.len,
@@ -110,8 +108,6 @@ pub fn deinit(astgen: *AstGen) void {
astgen.instructions.deinit(gpa);
astgen.extra.deinit(gpa);
astgen.string_bytes.deinit(gpa);
- astgen.decl_map.deinit(gpa);
- astgen.decls.deinit(gpa);
}
pub const ResultLoc = union(enum) {
@@ -124,6 +120,9 @@ pub const ResultLoc = union(enum) {
/// The expression must generate a pointer rather than a value. For example, the left hand side
/// of an assignment uses this kind of result location.
ref,
+ /// The callee will accept a ref, but it is not necessary, and the `ResultLoc`
+ /// may be treated as `none` instead.
+ none_or_ref,
/// The expression will be coerced into this type, but it will be evaluated as an rvalue.
ty: zir.Inst.Ref,
/// The expression must store its result into this typed pointer. The result instruction
@@ -157,7 +156,7 @@ pub const ResultLoc = union(enum) {
var elide_store_to_block_ptr_instructions = false;
switch (rl) {
// In this branch there will not be any store_to_block_ptr instructions.
- .discard, .none, .ty, .ref => return .{
+ .discard, .none, .none_or_ref, .ty, .ref => return .{
.tag = .break_operand,
.elide_store_to_block_ptr_instructions = false,
},
@@ -606,8 +605,13 @@ pub fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) Inn
.deref => {
const lhs = try expr(gz, scope, .none, node_datas[node].lhs);
- const result = try gz.addUnNode(.load, lhs, node);
- return rvalue(gz, scope, rl, result, node);
+ switch (rl) {
+ .ref, .none_or_ref => return lhs,
+ else => {
+ const result = try gz.addUnNode(.load, lhs, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+ }
},
.address_of => {
const result = try expr(gz, scope, .ref, node_datas[node].lhs);
@@ -816,10 +820,34 @@ pub fn structInitExpr(
}
switch (rl) {
.discard => return mod.failNode(scope, node, "TODO implement structInitExpr discard", .{}),
- .none => return mod.failNode(scope, node, "TODO implement structInitExpr none", .{}),
+ .none, .none_or_ref => return mod.failNode(scope, node, "TODO implement structInitExpr none", .{}),
.ref => unreachable, // struct literal not valid as l-value
.ty => |ty_inst| {
- return mod.failNode(scope, node, "TODO implement structInitExpr ty", .{});
+ const fields_list = try gpa.alloc(zir.Inst.StructInit.Item, struct_init.ast.fields.len);
+ defer gpa.free(fields_list);
+
+ for (struct_init.ast.fields) |field_init, i| {
+ const name_token = tree.firstToken(field_init) - 2;
+ const str_index = try gz.identAsString(name_token);
+
+ const field_ty_inst = try gz.addPlNode(.field_type, field_init, zir.Inst.FieldType{
+ .container_type = ty_inst,
+ .name_start = str_index,
+ });
+ fields_list[i] = .{
+ .field_type = astgen.refToIndex(field_ty_inst).?,
+ .init = try expr(gz, scope, .{ .ty = field_ty_inst }, field_init),
+ };
+ }
+ const init_inst = try gz.addPlNode(.struct_init, node, zir.Inst.StructInit{
+ .fields_len = @intCast(u32, fields_list.len),
+ });
+ try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
+ fields_list.len * @typeInfo(zir.Inst.StructInit.Item).Struct.fields.len);
+ for (fields_list) |field| {
+ _ = gz.astgen.addExtraAssumeCapacity(field);
+ }
+ return rvalue(gz, scope, rl, init_inst, node);
},
.ptr => |ptr_inst| {
const field_ptr_list = try gpa.alloc(zir.Inst.Index, struct_init.ast.fields.len);
@@ -1175,13 +1203,6 @@ fn blockExprStmts(
// in the above while loop.
const zir_tags = gz.astgen.instructions.items(.tag);
switch (zir_tags[inst]) {
- .@"const" => {
- const tv = gz.astgen.instructions.items(.data)[inst].@"const";
- break :b switch (tv.ty.zigTypeTag()) {
- .NoReturn, .Void => true,
- else => false,
- };
- },
// For some instructions, swap in a slightly different ZIR tag
// so we can avoid a separate ensure_result_used instruction.
.call_none_chkused => unreachable,
@@ -1248,7 +1269,10 @@ fn blockExprStmts(
.fn_type_var_args,
.fn_type_cc,
.fn_type_cc_var_args,
+ .has_decl,
.int,
+ .float,
+ .float128,
.intcast,
.int_type,
.is_non_null,
@@ -1321,12 +1345,18 @@ fn blockExprStmts(
.switch_capture_else,
.switch_capture_else_ref,
.struct_init_empty,
+ .struct_init,
+ .field_type,
.struct_decl,
.struct_decl_packed,
.struct_decl_extern,
.union_decl,
.enum_decl,
+ .enum_decl_nonexhaustive,
.opaque_decl,
+ .int_to_enum,
+ .enum_to_int,
+ .type_info,
=> break :b false,
// ZIR instructions that are always either `noreturn` or `void`.
@@ -1334,6 +1364,7 @@ fn blockExprStmts(
.dbg_stmt_node,
.ensure_result_used,
.ensure_result_non_error,
+ .@"export",
.set_eval_branch_quota,
.compile_log,
.ensure_err_payload_void,
@@ -1482,7 +1513,7 @@ fn varDecl(
init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node);
init_scope.rl_ty_inst = type_inst;
} else {
- const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node);
+ const alloc = try init_scope.addNode(.alloc_inferred, node);
resolve_inferred_alloc = alloc;
init_scope.rl_ptr = alloc;
}
@@ -1557,7 +1588,7 @@ fn varDecl(
const alloc = try gz.addUnNode(.alloc_mut, type_inst, node);
break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } };
} else a: {
- const alloc = try gz.addUnNode(.alloc_inferred_mut, undefined, node);
+ const alloc = try gz.addNode(.alloc_inferred_mut, node);
resolve_inferred_alloc = alloc;
break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc } };
};
@@ -1815,15 +1846,18 @@ fn containerDecl(
defer bit_bag.deinit(gpa);
var cur_bit_bag: u32 = 0;
- var member_index: usize = 0;
- while (true) {
- const member_node = container_decl.ast.members[member_index];
+ var field_index: usize = 0;
+ for (container_decl.ast.members) |member_node| {
const member = switch (node_tags[member_node]) {
.container_field_init => tree.containerFieldInit(member_node),
.container_field_align => tree.containerFieldAlign(member_node),
.container_field => tree.containerField(member_node),
- else => unreachable,
+ else => continue,
};
+ if (field_index % 16 == 0 and field_index != 0) {
+ try bit_bag.append(gpa, cur_bit_bag);
+ cur_bit_bag = 0;
+ }
if (member.comptime_token) |comptime_token| {
return mod.failTok(scope, comptime_token, "TODO implement comptime struct fields", .{});
}
@@ -1850,17 +1884,9 @@ fn containerDecl(
fields_data.appendAssumeCapacity(@enumToInt(default_inst));
}
- member_index += 1;
- if (member_index < container_decl.ast.members.len) {
- if (member_index % 16 == 0) {
- try bit_bag.append(gpa, cur_bit_bag);
- cur_bit_bag = 0;
- }
- } else {
- break;
- }
+ field_index += 1;
}
- const empty_slot_count = 16 - ((member_index - 1) % 16);
+ const empty_slot_count = 16 - (field_index % 16);
cur_bit_bag >>= @intCast(u5, empty_slot_count * 2);
const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{
@@ -1877,7 +1903,172 @@ fn containerDecl(
return mod.failTok(scope, container_decl.ast.main_token, "TODO AstGen for union decl", .{});
},
.keyword_enum => {
- return mod.failTok(scope, container_decl.ast.main_token, "TODO AstGen for enum decl", .{});
+ if (container_decl.layout_token) |t| {
+ return mod.failTok(scope, t, "enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", .{});
+ }
+ // Count total fields as well as how many have explicitly provided tag values.
+ const counts = blk: {
+ var values: usize = 0;
+ var total_fields: usize = 0;
+ var decls: usize = 0;
+ var nonexhaustive_node: ast.Node.Index = 0;
+ for (container_decl.ast.members) |member_node| {
+ const member = switch (node_tags[member_node]) {
+ .container_field_init => tree.containerFieldInit(member_node),
+ .container_field_align => tree.containerFieldAlign(member_node),
+ .container_field => tree.containerField(member_node),
+ else => {
+ decls += 1;
+ continue;
+ },
+ };
+ if (member.comptime_token) |comptime_token| {
+ return mod.failTok(scope, comptime_token, "enum fields cannot be marked comptime", .{});
+ }
+ if (member.ast.type_expr != 0) {
+ return mod.failNode(scope, member.ast.type_expr, "enum fields do not have types", .{});
+ }
+ // Alignment expressions in enums are caught by the parser.
+ assert(member.ast.align_expr == 0);
+
+ const name_token = member.ast.name_token;
+ if (mem.eql(u8, tree.tokenSlice(name_token), "_")) {
+ if (nonexhaustive_node != 0) {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ scope,
+ gz.nodeSrcLoc(member_node),
+ "redundant non-exhaustive enum mark",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ const other_src = gz.nodeSrcLoc(nonexhaustive_node);
+ try mod.errNote(scope, other_src, msg, "other mark here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ }
+ nonexhaustive_node = member_node;
+ if (member.ast.value_expr != 0) {
+ return mod.failNode(scope, member.ast.value_expr, "'_' is used to mark an enum as non-exhaustive and cannot be assigned a value", .{});
+ }
+ continue;
+ }
+ total_fields += 1;
+ if (member.ast.value_expr != 0) {
+ values += 1;
+ }
+ }
+ break :blk .{
+ .total_fields = total_fields,
+ .values = values,
+ .decls = decls,
+ .nonexhaustive_node = nonexhaustive_node,
+ };
+ };
+ if (counts.total_fields == 0) {
+ // One can construct an enum with no tags, and it functions the same as `noreturn`. But
+ // this is only useful for generic code; when explicitly using `enum {}` syntax, there
+ // must be at least one tag.
+ return mod.failNode(scope, node, "enum declarations must have at least one tag", .{});
+ }
+ if (counts.nonexhaustive_node != 0 and arg_inst == .none) {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ scope,
+ gz.nodeSrcLoc(node),
+ "non-exhaustive enum missing integer tag type",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ const other_src = gz.nodeSrcLoc(counts.nonexhaustive_node);
+ try mod.errNote(scope, other_src, msg, "marked non-exhaustive here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ }
+ if (counts.values == 0 and counts.decls == 0 and arg_inst == .none) {
+ // No explicitly provided tag values and no top level declarations! In this case,
+ // we can construct the enum type in AstGen and it will be correctly shared by all
+ // generic function instantiations and comptime function calls.
+ var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
+ errdefer new_decl_arena.deinit();
+ const arena = &new_decl_arena.allocator;
+
+ var fields_map: std.StringArrayHashMapUnmanaged(void) = .{};
+ try fields_map.ensureCapacity(arena, counts.total_fields);
+ for (container_decl.ast.members) |member_node| {
+ if (member_node == counts.nonexhaustive_node)
+ continue;
+ const member = switch (node_tags[member_node]) {
+ .container_field_init => tree.containerFieldInit(member_node),
+ .container_field_align => tree.containerFieldAlign(member_node),
+ .container_field => tree.containerField(member_node),
+ else => unreachable, // We checked earlier.
+ };
+ const name_token = member.ast.name_token;
+ const tag_name = try mod.identifierTokenStringTreeArena(
+ scope,
+ name_token,
+ tree,
+ arena,
+ );
+ const gop = fields_map.getOrPutAssumeCapacity(tag_name);
+ if (gop.found_existing) {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ scope,
+ gz.tokSrcLoc(name_token),
+ "duplicate enum tag",
+ .{},
+ );
+ errdefer msg.destroy(gpa);
+ // Iterate to find the other tag. We don't eagerly store it in a hash
+ // map because in the hot path there will be no compile error and we
+ // don't need to waste time with a hash map.
+ const bad_node = for (container_decl.ast.members) |other_member_node| {
+ const other_member = switch (node_tags[other_member_node]) {
+ .container_field_init => tree.containerFieldInit(other_member_node),
+ .container_field_align => tree.containerFieldAlign(other_member_node),
+ .container_field => tree.containerField(other_member_node),
+ else => unreachable, // We checked earlier.
+ };
+ const other_tag_name = try mod.identifierTokenStringTreeArena(
+ scope,
+ other_member.ast.name_token,
+ tree,
+ arena,
+ );
+ if (mem.eql(u8, tag_name, other_tag_name))
+ break other_member_node;
+ } else unreachable;
+ const other_src = gz.nodeSrcLoc(bad_node);
+ try mod.errNote(scope, other_src, msg, "other tag here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(scope, msg);
+ }
+ }
+ const enum_simple = try arena.create(Module.EnumSimple);
+ enum_simple.* = .{
+ .owner_decl = astgen.decl,
+ .node_offset = astgen.decl.nodeIndexToRelative(node),
+ .fields = fields_map,
+ };
+ const enum_ty = try Type.Tag.enum_simple.create(arena, enum_simple);
+ const enum_val = try Value.Tag.ty.create(arena, enum_ty);
+ const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
+ .ty = Type.initTag(.type),
+ .val = enum_val,
+ });
+ const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl);
+ const result = try gz.addDecl(.decl_val, decl_index, node);
+ return rvalue(gz, scope, rl, result, node);
+ }
+ // In this case we must generate ZIR code for the tag values, similar to
+ // how structs are handled above. The new anonymous Decl will be created in
+ // Sema, not AstGen.
+ return mod.failNode(scope, node, "TODO AstGen for enum decl with decls or explicitly provided field values", .{});
},
.keyword_opaque => {
const result = try gz.addNode(.opaque_decl, node);
@@ -1893,11 +2084,11 @@ fn errorSetDecl(
rl: ResultLoc,
node: ast.Node.Index,
) InnerError!zir.Inst.Ref {
- const mod = gz.astgen.mod;
+ const astgen = gz.astgen;
+ const mod = astgen.mod;
const tree = gz.tree();
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
- const arena = gz.astgen.arena;
// Count how many fields there are.
const error_token = main_tokens[node];
@@ -1914,6 +2105,11 @@ fn errorSetDecl(
} else unreachable; // TODO should not need else unreachable here
};
+ const gpa = mod.gpa;
+ var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
+ errdefer new_decl_arena.deinit();
+ const arena = &new_decl_arena.allocator;
+
const fields = try arena.alloc([]const u8, count);
{
var tok_i = error_token + 2;
@@ -1922,7 +2118,7 @@ fn errorSetDecl(
switch (token_tags[tok_i]) {
.doc_comment, .comma => {},
.identifier => {
- fields[field_i] = try mod.identifierTokenString(scope, tok_i);
+ fields[field_i] = try mod.identifierTokenStringTreeArena(scope, tok_i, tree, arena);
field_i += 1;
},
.r_brace => break,
@@ -1932,18 +2128,19 @@ fn errorSetDecl(
}
const error_set = try arena.create(Module.ErrorSet);
error_set.* = .{
- .owner_decl = gz.astgen.decl,
- .node_offset = gz.astgen.decl.nodeIndexToRelative(node),
+ .owner_decl = astgen.decl,
+ .node_offset = astgen.decl.nodeIndexToRelative(node),
.names_ptr = fields.ptr,
.names_len = @intCast(u32, fields.len),
};
const error_set_ty = try Type.Tag.error_set.create(arena, error_set);
- const typed_value = try arena.create(TypedValue);
- typed_value.* = .{
+ const error_set_val = try Value.Tag.ty.create(arena, error_set_ty);
+ const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{
.ty = Type.initTag(.type),
- .val = try Value.Tag.ty.create(arena, error_set_ty),
- };
- const result = try gz.addConst(typed_value);
+ .val = error_set_val,
+ });
+ const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl);
+ const result = try gz.addDecl(.decl_val, decl_index, node);
return rvalue(gz, scope, rl, result, node);
}
@@ -1980,7 +2177,7 @@ fn orelseCatchExpr(
// TODO handle catch
const operand_rl: ResultLoc = switch (block_scope.break_result_loc) {
.ref => .ref,
- .discard, .none, .block_ptr, .inferred_ptr => .none,
+ .discard, .none, .none_or_ref, .block_ptr, .inferred_ptr => .none,
.ty => |elem_ty| blk: {
const wrapped_ty = try block_scope.addUnNode(.optional_type, elem_ty, node);
break :blk .{ .ty = wrapped_ty };
@@ -2156,7 +2353,7 @@ pub fn fieldAccess(
.field_name_start = str_index,
}),
else => return rvalue(gz, scope, rl, try gz.addPlNode(.field_val, node, zir.Inst.Field{
- .lhs = try expr(gz, scope, .none, object_node),
+ .lhs = try expr(gz, scope, .none_or_ref, object_node),
.field_name_start = str_index,
}), node),
}
@@ -2179,7 +2376,7 @@ fn arrayAccess(
),
else => return rvalue(gz, scope, rl, try gz.addBin(
.elem_val,
- try expr(gz, scope, .none, node_datas[node].lhs),
+ try expr(gz, scope, .none_or_ref, node_datas[node].lhs),
try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs),
), node),
}
@@ -3188,8 +3385,13 @@ fn switchExpr(
switch (strat.tag) {
.break_operand => {
// Switch expressions return `true` for `nodeMayNeedMemoryLocation` thus
- // this is always true.
- assert(strat.elide_store_to_block_ptr_instructions);
+ // `elide_store_to_block_ptr_instructions` will either be true,
+ // or all prongs are noreturn.
+ if (!strat.elide_store_to_block_ptr_instructions) {
+ astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items);
+ astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items);
+ return astgen.indexToRef(switch_block);
+ }
// There will necessarily be a store_to_block_ptr for
// all prongs, except for prongs that ended with a noreturn instruction.
@@ -3418,7 +3620,8 @@ fn identifier(
const tracy = trace(@src());
defer tracy.end();
- const mod = gz.astgen.mod;
+ const astgen = gz.astgen;
+ const mod = astgen.mod;
const tree = gz.tree();
const main_tokens = tree.nodes.items(.main_token);
@@ -3451,7 +3654,7 @@ fn identifier(
const result = try gz.add(.{
.tag = .int_type,
.data = .{ .int_type = .{
- .src_node = gz.astgen.decl.nodeIndexToRelative(ident),
+ .src_node = astgen.decl.nodeIndexToRelative(ident),
.signedness = signedness,
.bit_count = bit_count,
} },
@@ -3474,9 +3677,13 @@ fn identifier(
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (mem.eql(u8, local_ptr.name, ident_name)) {
- if (rl == .ref) return local_ptr.ptr;
- const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident);
- return rvalue(gz, scope, rl, loaded, ident);
+ switch (rl) {
+ .ref, .none_or_ref => return local_ptr.ptr,
+ else => {
+ const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident);
+ return rvalue(gz, scope, rl, loaded, ident);
+ },
+ }
}
s = local_ptr.parent;
},
@@ -3485,15 +3692,15 @@ fn identifier(
};
}
- const gop = try gz.astgen.decl_map.getOrPut(mod.gpa, ident_name);
- if (!gop.found_existing) {
- const decl = mod.lookupDeclName(scope, ident_name) orelse
- return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name});
- try gz.astgen.decls.append(mod.gpa, decl);
- }
- const decl_index = @intCast(u32, gop.index);
+ const decl = mod.lookupDeclName(scope, ident_name) orelse {
+ // TODO insert a "dependency on the non-existence of a decl" here to make this
+ // compile error go away when the decl is introduced. This data should be in a global
+ // sparse map since it is only relevant when a compile error occurs.
+ return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name});
+ };
+ const decl_index = try mod.declareDeclDependency(astgen.decl, decl);
switch (rl) {
- .ref => return gz.addDecl(.decl_ref, decl_index, ident),
+ .ref, .none_or_ref => return gz.addDecl(.decl_ref, decl_index, ident),
else => return rvalue(gz, scope, rl, try gz.addDecl(.decl_val, decl_index, ident), ident),
}
}
@@ -3626,12 +3833,23 @@ fn floatLiteral(
const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) {
error.InvalidCharacter => unreachable, // validated by tokenizer
};
- const typed_value = try arena.create(TypedValue);
- typed_value.* = .{
- .ty = Type.initTag(.comptime_float),
- .val = try Value.Tag.float_128.create(arena, float_number),
- };
- const result = try gz.addConst(typed_value);
+ // If the value fits into a f32 without losing any precision, store it that way.
+ @setFloatMode(.Strict);
+ const smaller_float = @floatCast(f32, float_number);
+ const bigger_again: f128 = smaller_float;
+ if (bigger_again == float_number) {
+ const result = try gz.addFloat(smaller_float, node);
+ return rvalue(gz, scope, rl, result, node);
+ }
+ // We need to use 128 bits. Break the float into 4 u32 values so we can
+ // put it into the `extra` array.
+ const int_bits = @bitCast(u128, float_number);
+ const result = try gz.addPlNode(.float128, node, zir.Inst.Float128{
+ .piece0 = @truncate(u32, int_bits),
+ .piece1 = @truncate(u32, int_bits >> 32),
+ .piece2 = @truncate(u32, int_bits >> 64),
+ .piece3 = @truncate(u32, int_bits >> 96),
+ });
return rvalue(gz, scope, rl, result, node);
}
@@ -3697,7 +3915,7 @@ fn as(
) InnerError!zir.Inst.Ref {
const dest_type = try typeExpr(gz, scope, lhs);
switch (rl) {
- .none, .discard, .ref, .ty => {
+ .none, .none_or_ref, .discard, .ref, .ty => {
const result = try expr(gz, scope, .{ .ty = dest_type }, rhs);
return rvalue(gz, scope, rl, result, node);
},
@@ -3781,7 +3999,7 @@ fn bitCast(
});
return rvalue(gz, scope, rl, result, node);
},
- .ref => unreachable, // `@bitCast` is not allowed as an r-value.
+ .ref, .none_or_ref => unreachable, // `@bitCast` is not allowed as an r-value.
.ptr => |result_ptr| {
const casted_result_ptr = try gz.addUnNode(.bitcast_result_ptr, result_ptr, node);
return expr(gz, scope, .{ .ptr = casted_result_ptr }, rhs);
@@ -3882,11 +4100,11 @@ fn builtinCall(
return rvalue(gz, scope, rl, result, node);
},
.breakpoint => {
- const result = try gz.add(.{
+ _ = try gz.add(.{
.tag = .breakpoint,
.data = .{ .node = gz.astgen.decl.nodeIndexToRelative(node) },
});
- return rvalue(gz, scope, rl, result, node);
+ return rvalue(gz, scope, rl, .void_value, node);
},
.import => {
const target = try expr(gz, scope, .none, params[0]);
@@ -3943,6 +4161,50 @@ fn builtinCall(
.bit_cast => return bitCast(gz, scope, rl, node, params[0], params[1]),
.TypeOf => return typeOf(gz, scope, rl, node, params),
+ .int_to_enum => {
+ const result = try gz.addPlNode(.int_to_enum, node, zir.Inst.Bin{
+ .lhs = try typeExpr(gz, scope, params[0]),
+ .rhs = try expr(gz, scope, .none, params[1]),
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+
+ .enum_to_int => {
+ const operand = try expr(gz, scope, .none, params[0]);
+ const result = try gz.addUnNode(.enum_to_int, operand, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+
+ .@"export" => {
+ // TODO: @export is supposed to be able to export things other than functions.
+ // Instead of `comptimeExpr` here we need `decl_ref`.
+ const fn_to_export = try comptimeExpr(gz, scope, .none, params[0]);
+ // TODO: the second parameter here is supposed to be
+ // `std.builtin.ExportOptions`, not a string.
+ const export_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]);
+ _ = try gz.addPlNode(.@"export", node, zir.Inst.Bin{
+ .lhs = fn_to_export,
+ .rhs = export_name,
+ });
+ return rvalue(gz, scope, rl, .void_value, node);
+ },
+
+ .has_decl => {
+ const container_type = try typeExpr(gz, scope, params[0]);
+ const name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]);
+ const result = try gz.addPlNode(.has_decl, node, zir.Inst.Bin{
+ .lhs = container_type,
+ .rhs = name,
+ });
+ return rvalue(gz, scope, rl, result, node);
+ },
+
+ .type_info => {
+ const operand = try typeExpr(gz, scope, params[0]);
+ const result = try gz.addUnNode(.type_info, operand, node);
+ return rvalue(gz, scope, rl, result, node);
+ },
+
.add_with_overflow,
.align_cast,
.align_of,
@@ -3969,17 +4231,13 @@ fn builtinCall(
.div_floor,
.div_trunc,
.embed_file,
- .enum_to_int,
.error_name,
.error_return_trace,
.err_set_cast,
- .@"export",
.fence,
.field_parent_ptr,
.float_to_int,
- .has_decl,
.has_field,
- .int_to_enum,
.int_to_float,
.int_to_ptr,
.memcpy,
@@ -4023,7 +4281,6 @@ fn builtinCall(
.This,
.truncate,
.Type,
- .type_info,
.type_name,
.union_init,
=> return mod.failNode(scope, node, "TODO: implement builtin function {s}", .{
@@ -4354,7 +4611,7 @@ fn rvalue(
src_node: ast.Node.Index,
) InnerError!zir.Inst.Ref {
switch (rl) {
- .none => return result,
+ .none, .none_or_ref => return result,
.discard => {
// Emit a compile error for discarding error values.
_ = try gz.addUnNode(.ensure_result_non_error, result, src_node);
diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig
@@ -484,7 +484,7 @@ pub const list = list: {
"@intToEnum",
.{
.tag = .int_to_enum,
- .param_count = 1,
+ .param_count = 2,
},
},
.{
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -272,32 +272,57 @@ pub const AllErrors = struct {
line: u32,
column: u32,
byte_offset: u32,
+ /// Does not include the trailing newline.
+ source_line: ?[]const u8,
notes: []Message = &.{},
},
plain: struct {
msg: []const u8,
},
- pub fn renderToStdErr(msg: Message) void {
- return msg.renderToStdErrInner("error");
+ pub fn renderToStdErr(msg: Message, ttyconf: std.debug.TTY.Config) void {
+ const stderr_mutex = std.debug.getStderrMutex();
+ const held = std.debug.getStderrMutex().acquire();
+ defer held.release();
+ const stderr = std.io.getStdErr();
+ return msg.renderToStdErrInner(ttyconf, stderr, "error:", .Red) catch return;
}
- fn renderToStdErrInner(msg: Message, kind: []const u8) void {
+ fn renderToStdErrInner(
+ msg: Message,
+ ttyconf: std.debug.TTY.Config,
+ stderr_file: std.fs.File,
+ kind: []const u8,
+ color: std.debug.TTY.Color,
+ ) anyerror!void {
+ const stderr = stderr_file.writer();
switch (msg) {
.src => |src| {
- std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{
+ ttyconf.setColor(stderr, .Bold);
+ try stderr.print("{s}:{d}:{d}: ", .{
src.src_path,
src.line + 1,
src.column + 1,
- kind,
- src.msg,
});
+ ttyconf.setColor(stderr, color);
+ try stderr.writeAll(kind);
+ ttyconf.setColor(stderr, .Bold);
+ try stderr.print(" {s}\n", .{src.msg});
+ ttyconf.setColor(stderr, .Reset);
+ if (src.source_line) |line| {
+ try stderr.writeAll(line);
+ try stderr.writeByte('\n');
+ try stderr.writeByteNTimes(' ', src.column);
+ ttyconf.setColor(stderr, .Green);
+ try stderr.writeAll("^\n");
+ ttyconf.setColor(stderr, .Reset);
+ }
for (src.notes) |note| {
- note.renderToStdErrInner("note");
+ try note.renderToStdErrInner(ttyconf, stderr_file, "note:", .Cyan);
}
},
.plain => |plain| {
- std.debug.print("{s}: {s}\n", .{ kind, plain.msg });
+ try stderr.print("{s}: {s}\n", .{ kind, plain.msg });
},
}
}
@@ -327,6 +352,7 @@ pub const AllErrors = struct {
.byte_offset = byte_offset,
.line = @intCast(u32, loc.line),
.column = @intCast(u32, loc.column),
+ .source_line = try arena.allocator.dupe(u8, loc.source_line),
},
};
}
@@ -342,6 +368,7 @@ pub const AllErrors = struct {
.line = @intCast(u32, loc.line),
.column = @intCast(u32, loc.column),
.notes = notes,
+ .source_line = try arena.allocator.dupe(u8, loc.source_line),
},
});
}
@@ -906,38 +933,56 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
artifact_sub_dir,
};
- // TODO when we implement serialization and deserialization of incremental compilation metadata,
- // this is where we would load it. We have open a handle to the directory where
- // the output either already is, or will be.
+ // If we rely on stage1, we must not redundantly add these packages.
+ const use_stage1 = build_options.is_stage1 and use_llvm;
+ if (!use_stage1) {
+ const builtin_pkg = try Package.createWithDir(
+ gpa,
+ zig_cache_artifact_directory,
+ null,
+ "builtin.zig",
+ );
+ errdefer builtin_pkg.destroy(gpa);
+
+ const std_pkg = try Package.createWithDir(
+ gpa,
+ options.zig_lib_directory,
+ "std",
+ "std.zig",
+ );
+ errdefer std_pkg.destroy(gpa);
+
+ try root_pkg.addAndAdopt(gpa, "builtin", builtin_pkg);
+ try root_pkg.add(gpa, "root", root_pkg);
+ try root_pkg.addAndAdopt(gpa, "std", std_pkg);
+
+ try std_pkg.add(gpa, "builtin", builtin_pkg);
+ try std_pkg.add(gpa, "root", root_pkg);
+ }
+
+ // TODO when we implement serialization and deserialization of incremental
+ // compilation metadata, this is where we would load it. We have open a handle
+ // to the directory where the output either already is, or will be.
// However we currently do not have serialization of such metadata, so for now
// we set up an empty Module that does the entire compilation fresh.
- const root_scope = rs: {
- if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) {
- const root_scope = try gpa.create(Module.Scope.File);
- const struct_ty = try Type.Tag.empty_struct.create(
- gpa,
- &root_scope.root_container,
- );
- root_scope.* = .{
- // TODO this is duped so it can be freed in Container.deinit
- .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path),
- .source = .{ .unloaded = {} },
- .tree = undefined,
- .status = .never_loaded,
- .pkg = root_pkg,
- .root_container = .{
- .file_scope = root_scope,
- .decls = .{},
- .ty = struct_ty,
- },
- };
- break :rs root_scope;
- } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) {
- return error.ZirFilesUnsupported;
- } else {
- unreachable;
- }
+ const root_scope = try gpa.create(Module.Scope.File);
+ errdefer gpa.destroy(root_scope);
+
+ const struct_ty = try Type.Tag.empty_struct.create(gpa, &root_scope.root_container);
+ root_scope.* = .{
+ // TODO this is duped so it can be freed in Container.deinit
+ .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path),
+ .source = .{ .unloaded = {} },
+ .tree = undefined,
+ .status = .never_loaded,
+ .pkg = root_pkg,
+ .root_container = .{
+ .file_scope = root_scope,
+ .decls = .{},
+ .ty = struct_ty,
+ .parent_name_hash = root_pkg.namespace_hash,
+ },
};
const module = try arena.create(Module);
@@ -1339,16 +1384,17 @@ pub fn update(self: *Compilation) !void {
self.c_object_work_queue.writeItemAssumeCapacity(entry.key);
}
- const use_stage1 = build_options.omit_stage2 or build_options.is_stage1 and self.bin_file.options.use_llvm;
+ const use_stage1 = build_options.omit_stage2 or
+ (build_options.is_stage1 and self.bin_file.options.use_llvm);
if (!use_stage1) {
if (self.bin_file.options.module) |module| {
module.compile_log_text.shrinkAndFree(module.gpa, 0);
module.generation += 1;
// TODO Detect which source files changed.
- // Until then we simulate a full cache miss. Source files could have been loaded for any reason;
- // to force a refresh we unload now.
- module.root_scope.unload(module.gpa);
+ // Until then we simulate a full cache miss. Source files could have been loaded
+ // for any reason; to force a refresh we unload now.
+ module.unloadFile(module.root_scope);
module.failed_root_src_file = null;
module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) {
error.AnalysisFail => {
@@ -1362,7 +1408,7 @@ pub fn update(self: *Compilation) !void {
// TODO only analyze imports if they are still referenced
for (module.import_table.items()) |entry| {
- entry.value.unload(module.gpa);
+ module.unloadFile(entry.value);
module.analyzeContainer(&entry.value.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
@@ -1377,14 +1423,17 @@ pub fn update(self: *Compilation) !void {
if (!use_stage1) {
if (self.bin_file.options.module) |module| {
- // Process the deletion set.
- while (module.deletion_set.popOrNull()) |decl| {
- if (decl.dependants.items().len != 0) {
- decl.deletion_flag = false;
- continue;
- }
- try module.deleteDecl(decl);
+ // Process the deletion set. We use a while loop here because the
+ // deletion set may grow as we call `deleteDecl` within this loop,
+ // and more unreferenced Decls are revealed.
+ var entry_i: usize = 0;
+ while (entry_i < module.deletion_set.entries.items.len) : (entry_i += 1) {
+ const decl = module.deletion_set.entries.items[entry_i].key;
+ assert(decl.deletion_flag);
+ assert(decl.dependants.items().len == 0);
+ try module.deleteDecl(decl, null);
}
+ module.deletion_set.shrinkRetainingCapacity(0);
}
}
@@ -1429,11 +1478,25 @@ pub fn totalErrorCount(self: *Compilation) usize {
var total: usize = self.failed_c_objects.items().len;
if (self.bin_file.options.module) |module| {
- total += module.failed_decls.count() +
- module.emit_h_failed_decls.count() +
- module.failed_exports.items().len +
+ total += module.failed_exports.items().len +
module.failed_files.items().len +
@boolToInt(module.failed_root_src_file != null);
+ // Skip errors for Decls within files that failed parsing.
+ // When a parse error is introduced, we keep all the semantic analysis for
+ // the previous parse success, including compile errors, but we cannot
+ // emit them until the file succeeds parsing.
+ for (module.failed_decls.items()) |entry| {
+ if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
+ continue;
+ }
+ total += 1;
+ }
+ for (module.emit_h_failed_decls.items()) |entry| {
+ if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
+ continue;
+ }
+ total += 1;
+ }
}
// The "no entry point found" error only counts if there are no other errors.
@@ -1472,6 +1535,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
.byte_offset = 0,
.line = err_msg.line,
.column = err_msg.column,
+ .source_line = null, // TODO
},
});
}
@@ -1480,9 +1544,19 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
try AllErrors.add(module, &arena, &errors, entry.value.*);
}
for (module.failed_decls.items()) |entry| {
+ if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
+ // Skip errors for Decls within files that had a parse failure.
+ // We'll try again once parsing succeeds.
+ continue;
+ }
try AllErrors.add(module, &arena, &errors, entry.value.*);
}
for (module.emit_h_failed_decls.items()) |entry| {
+ if (entry.key.container.file_scope.status == .unloaded_parse_failure) {
+ // Skip errors for Decls within files that had a parse failure.
+ // We'll try again once parsing succeeds.
+ continue;
+ }
try AllErrors.add(module, &arena, &errors, entry.value.*);
}
for (module.failed_exports.items()) |entry| {
@@ -2437,7 +2511,7 @@ pub fn addCCArgs(
try argv.append("-fPIC");
}
},
- .shared_library, .assembly, .ll, .bc, .unknown, .static_library, .object, .zig, .zir => {},
+ .shared_library, .assembly, .ll, .bc, .unknown, .static_library, .object, .zig => {},
}
if (out_dep_path) |p| {
try argv.appendSlice(&[_][]const u8{ "-MD", "-MV", "-MF", p });
@@ -2511,7 +2585,6 @@ pub const FileExt = enum {
object,
static_library,
zig,
- zir,
unknown,
pub fn clangSupportsDepFile(ext: FileExt) bool {
@@ -2525,7 +2598,6 @@ pub const FileExt = enum {
.object,
.static_library,
.zig,
- .zir,
.unknown,
=> false,
};
@@ -2597,8 +2669,6 @@ pub fn classifyFileExt(filename: []const u8) FileExt {
return .h;
} else if (mem.endsWith(u8, filename, ".zig")) {
return .zig;
- } else if (mem.endsWith(u8, filename, ".zir")) {
- return .zir;
} else if (hasSharedLibraryExt(filename)) {
return .shared_library;
} else if (hasStaticLibraryExt(filename)) {
@@ -2619,7 +2689,6 @@ test "classifyFileExt" {
std.testing.expectEqual(FileExt.shared_library, classifyFileExt("foo.so.1.2.3"));
std.testing.expectEqual(FileExt.unknown, classifyFileExt("foo.so.1.2.3~"));
std.testing.expectEqual(FileExt.zig, classifyFileExt("foo.zig"));
- std.testing.expectEqual(FileExt.zir, classifyFileExt("foo.zir"));
}
fn haveFramePointer(comp: *const Compilation) bool {
@@ -2814,6 +2883,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8
const target = comp.getTarget();
const generic_arch_name = target.cpu.arch.genericName();
+ const use_stage1 = build_options.omit_stage2 or
+ (build_options.is_stage1 and comp.bin_file.options.use_llvm);
@setEvalBranchQuota(4000);
try buffer.writer().print(
@@ -2826,6 +2897,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8
\\/// Zig version. When writing code that supports multiple versions of Zig, prefer
\\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
\\pub const zig_version = try @import("std").SemanticVersion.parse("{s}");
+ \\pub const zig_is_stage2 = {};
\\
\\pub const output_mode = OutputMode.{};
\\pub const link_mode = LinkMode.{};
@@ -2839,6 +2911,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8
\\
, .{
build_options.version,
+ !use_stage1,
std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)),
std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)),
comp.bin_file.options.is_test,
@@ -3044,6 +3117,7 @@ fn buildOutputFromZig(
.handle = special_dir,
},
.root_src_path = src_basename,
+ .namespace_hash = Package.root_namespace_hash,
};
const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len];
const target = comp.getTarget();
diff --git a/src/Module.zig b/src/Module.zig
@@ -65,8 +65,8 @@ emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{},
/// Keep track of one `@compileLog` callsite per owner Decl.
compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, SrcLoc) = .{},
/// Using a map here for consistency with the other fields here.
-/// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator.
-failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{},
+/// The ErrorMsg memory is owned by the `Scope.File`, using Module's general purpose allocator.
+failed_files: std.AutoArrayHashMapUnmanaged(*Scope.File, *ErrorMsg) = .{},
/// Using a map here for consistency with the other fields here.
/// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator.
failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},
@@ -75,7 +75,7 @@ next_anon_name_index: usize = 0,
/// Candidates for deletion. After a semantic analysis update completes, this list
/// contains Decls that need to be deleted if they end up having no references to them.
-deletion_set: ArrayListUnmanaged(*Decl) = .{},
+deletion_set: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
/// Error tags and their values, tag names are duped with mod.gpa.
/// Corresponds with `error_name_list`.
@@ -150,9 +150,15 @@ pub const Decl = struct {
/// The direct parent container of the Decl.
/// Reference to externally owned memory.
container: *Scope.Container,
- /// The AST Node decl index or ZIR Inst index that contains this declaration.
+
+ /// An integer that can be checked against the corresponding incrementing
+ /// generation field of Module. This is used to determine whether `complete` status
+ /// represents pre- or post- re-analysis.
+ generation: u32,
+ /// The AST Node index or ZIR Inst index that contains this declaration.
/// Must be recomputed when the corresponding source file is modified.
- src_index: usize,
+ src_node: ast.Node.Index,
+
/// The most recent value of the Decl after a successful semantic analysis.
typed_value: union(enum) {
never_succeeded: void,
@@ -192,17 +198,12 @@ pub const Decl = struct {
/// to require re-analysis.
outdated,
},
- /// This flag is set when this Decl is added to a check_for_deletion set, and cleared
+ /// This flag is set when this Decl is added to `Module.deletion_set`, and cleared
/// when removed.
deletion_flag: bool,
/// Whether the corresponding AST decl has a `pub` keyword.
is_pub: bool,
- /// An integer that can be checked against the corresponding incrementing
- /// generation field of Module. This is used to determine whether `complete` status
- /// represents pre- or post- re-analysis.
- generation: u32,
-
/// Represents the position of the code in the output file.
/// This is populated regardless of semantic analysis and code generation.
link: link.File.LinkBlock,
@@ -249,11 +250,11 @@ pub const Decl = struct {
}
pub fn relativeToNodeIndex(decl: Decl, offset: i32) ast.Node.Index {
- return @bitCast(ast.Node.Index, offset + @bitCast(i32, decl.srcNode()));
+ return @bitCast(ast.Node.Index, offset + @bitCast(i32, decl.src_node));
}
pub fn nodeIndexToRelative(decl: Decl, node_index: ast.Node.Index) i32 {
- return @bitCast(i32, node_index) - @bitCast(i32, decl.srcNode());
+ return @bitCast(i32, node_index) - @bitCast(i32, decl.src_node);
}
pub fn tokSrcLoc(decl: Decl, token_index: ast.TokenIndex) LazySrcLoc {
@@ -271,14 +272,9 @@ pub const Decl = struct {
};
}
- pub fn srcNode(decl: Decl) u32 {
- const tree = &decl.container.file_scope.tree;
- return tree.rootDecls()[decl.src_index];
- }
-
pub fn srcToken(decl: Decl) u32 {
const tree = &decl.container.file_scope.tree;
- return tree.firstToken(decl.srcNode());
+ return tree.firstToken(decl.src_node);
}
pub fn srcByteOffset(decl: Decl) u32 {
@@ -290,6 +286,18 @@ pub const Decl = struct {
return decl.container.fullyQualifiedNameHash(mem.spanZ(decl.name));
}
+ pub fn renderFullyQualifiedName(decl: Decl, writer: anytype) !void {
+ const unqualified_name = mem.spanZ(decl.name);
+ return decl.container.renderFullyQualifiedName(unqualified_name, writer);
+ }
+
+ pub fn getFullyQualifiedName(decl: Decl, gpa: *Allocator) ![]u8 {
+ var buffer = std.ArrayList(u8).init(gpa);
+ defer buffer.deinit();
+ try decl.renderFullyQualifiedName(buffer.writer());
+ return buffer.toOwnedSlice();
+ }
+
pub fn typedValue(decl: *Decl) error{AnalysisFail}!TypedValue {
const tvm = decl.typedValueManaged() orelse return error.AnalysisFail;
return tvm.typed_value;
@@ -354,6 +362,13 @@ pub const ErrorSet = struct {
/// The string bytes are stored in the owner Decl arena.
/// They are in the same order they appear in the AST.
names_ptr: [*]const []const u8,
+
+ pub fn srcLoc(self: ErrorSet) SrcLoc {
+ return .{
+ .container = .{ .decl = self.owner_decl },
+ .lazy = .{ .node_offset = self.node_offset },
+ };
+ }
};
/// Represents the data that a struct declaration provides.
@@ -364,7 +379,7 @@ pub const Struct = struct {
/// Represents the declarations inside this struct.
container: Scope.Container,
- /// Offset from Decl node index, points to the struct AST node.
+ /// Offset from `owner_decl`, points to the struct AST node.
node_offset: i32,
pub const Field = struct {
@@ -373,6 +388,64 @@ pub const Struct = struct {
/// Uses `unreachable_value` to indicate no default.
default_val: Value,
};
+
+ pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![]u8 {
+ return s.owner_decl.getFullyQualifiedName(gpa);
+ }
+
+ pub fn srcLoc(s: Struct) SrcLoc {
+ return .{
+ .container = .{ .decl = s.owner_decl },
+ .lazy = .{ .node_offset = s.node_offset },
+ };
+ }
+};
+
+/// Represents the data that an enum declaration provides, when the fields
+/// are auto-numbered, and there are no declarations. The integer tag type
+/// is inferred to be the smallest power of two unsigned int that fits
+/// the number of fields.
+pub const EnumSimple = struct {
+ owner_decl: *Decl,
+ /// Set of field names in declaration order.
+ fields: std.StringArrayHashMapUnmanaged(void),
+ /// Offset from `owner_decl`, points to the enum decl AST node.
+ node_offset: i32,
+
+ pub fn srcLoc(self: EnumSimple) SrcLoc {
+ return .{
+ .container = .{ .decl = self.owner_decl },
+ .lazy = .{ .node_offset = self.node_offset },
+ };
+ }
+};
+
+/// Represents the data that an enum declaration provides, when there is
+/// at least one tag value explicitly specified, or at least one declaration.
+pub const EnumFull = struct {
+ owner_decl: *Decl,
+ /// An integer type which is used for the numerical value of the enum.
+ /// Whether zig chooses this type or the user specifies it, it is stored here.
+ tag_ty: Type,
+ /// Set of field names in declaration order.
+ fields: std.StringArrayHashMapUnmanaged(void),
+ /// Maps integer tag value to field index.
+ /// Entries are in declaration order, same as `fields`.
+ /// If this hash map is empty, it means the enum tags are auto-numbered.
+ values: ValueMap,
+ /// Represents the declarations inside this struct.
+ container: Scope.Container,
+ /// Offset from `owner_decl`, points to the enum decl AST node.
+ node_offset: i32,
+
+ pub const ValueMap = std.ArrayHashMapUnmanaged(Value, void, Value.hash_u32, Value.eql, false);
+
+ pub fn srcLoc(self: EnumFull) SrcLoc {
+ return .{
+ .container = .{ .decl = self.owner_decl },
+ .lazy = .{ .node_offset = self.node_offset },
+ };
+ }
};
/// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
@@ -601,6 +674,7 @@ pub const Scope = struct {
base: Scope = Scope{ .tag = base_tag },
file_scope: *Scope.File,
+ parent_name_hash: NameHash,
/// Direct children of the file.
decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
@@ -619,8 +693,12 @@ pub const Scope = struct {
}
pub fn fullyQualifiedNameHash(cont: *Container, name: []const u8) NameHash {
- // TODO container scope qualified names.
- return std.zig.hashSrc(name);
+ return std.zig.hashName(cont.parent_name_hash, ".", name);
+ }
+
+ pub fn renderFullyQualifiedName(cont: Container, name: []const u8, writer: anytype) !void {
+ // TODO this should render e.g. "std.fs.Dir.OpenOptions"
+ return writer.writeAll(name);
}
};
@@ -650,10 +728,12 @@ pub const Scope = struct {
pub fn unload(file: *File, gpa: *Allocator) void {
switch (file.status) {
- .never_loaded,
.unloaded_parse_failure,
+ .never_loaded,
.unloaded_success,
- => {},
+ => {
+ file.status = .unloaded_success;
+ },
.loaded_success => {
file.tree.deinit(gpa);
@@ -1018,7 +1098,6 @@ pub const Scope = struct {
.instructions = gz.astgen.instructions.toOwnedSlice(),
.string_bytes = gz.astgen.string_bytes.toOwnedSlice(gpa),
.extra = gz.astgen.extra.toOwnedSlice(gpa),
- .decls = gz.astgen.decls.toOwnedSlice(gpa),
};
}
@@ -1048,6 +1127,9 @@ pub const Scope = struct {
gz.rl_ty_inst = ty_inst;
gz.break_result_loc = parent_rl;
},
+ .none_or_ref => {
+ gz.break_result_loc = .ref;
+ },
.discard, .none, .ptr, .ref => {
gz.break_result_loc = parent_rl;
},
@@ -1227,6 +1309,16 @@ pub const Scope = struct {
});
}
+ pub fn addFloat(gz: *GenZir, number: f32, src_node: ast.Node.Index) !zir.Inst.Ref {
+ return gz.add(.{
+ .tag = .float,
+ .data = .{ .float = .{
+ .src_node = gz.astgen.decl.nodeIndexToRelative(src_node),
+ .number = number,
+ } },
+ });
+ }
+
pub fn addUnNode(
gz: *GenZir,
tag: zir.Inst.Tag,
@@ -1435,13 +1527,6 @@ pub const Scope = struct {
return new_index;
}
- pub fn addConst(gz: *GenZir, typed_value: *TypedValue) !zir.Inst.Ref {
- return gz.add(.{
- .tag = .@"const",
- .data = .{ .@"const" = typed_value },
- });
- }
-
pub fn add(gz: *GenZir, inst: zir.Inst) !zir.Inst.Ref {
return gz.astgen.indexToRef(try gz.addAsIndex(inst));
}
@@ -1572,6 +1657,7 @@ pub const SrcLoc = struct {
.byte_offset,
.token_offset,
.node_offset,
+ .node_offset_back2tok,
.node_offset_var_decl_ty,
.node_offset_for_cond,
.node_offset_builtin_call_arg0,
@@ -1633,6 +1719,14 @@ pub const SrcLoc = struct {
const token_starts = tree.tokens.items(.start);
return token_starts[tok_index];
},
+ .node_offset_back2tok => |node_off| {
+ const decl = src_loc.container.decl;
+ const node = decl.relativeToNodeIndex(node_off);
+ const tree = decl.container.file_scope.base.tree();
+ const tok_index = tree.firstToken(node) - 2;
+ const token_starts = tree.tokens.items(.start);
+ return token_starts[tok_index];
+ },
.node_offset_var_decl_ty => |node_off| {
const decl = src_loc.container.decl;
const node = decl.relativeToNodeIndex(node_off);
@@ -1747,7 +1841,10 @@ pub const SrcLoc = struct {
const node_datas = tree.nodes.items(.data);
const node_tags = tree.nodes.items(.tag);
const node = decl.relativeToNodeIndex(node_off);
- const tok_index = node_datas[node].rhs;
+ const tok_index = switch (node_tags[node]) {
+ .field_access => node_datas[node].rhs,
+ else => tree.firstToken(node) - 2,
+ };
const token_starts = tree.tokens.items(.start);
return token_starts[tok_index];
},
@@ -1988,6 +2085,10 @@ pub const LazySrcLoc = union(enum) {
/// from its containing Decl node AST index.
/// The Decl is determined contextually.
node_offset: i32,
+ /// The source location points to two tokens left of the first token of an AST node,
+ /// which is this value offset from its containing Decl node AST index.
+ /// The Decl is determined contextually.
+ node_offset_back2tok: i32,
/// The source location points to a variable declaration type expression,
/// found by taking this AST node index offset from the containing
/// Decl AST node, which points to a variable declaration AST node. Next, navigate
@@ -2026,10 +2127,10 @@ pub const LazySrcLoc = union(enum) {
/// to the callee expression.
/// The Decl is determined contextually.
node_offset_call_func: i32,
- /// The source location points to the field name of a field access expression,
- /// found by taking this AST node index offset from the containing
- /// Decl AST node, which points to a field access AST node. Next, navigate
- /// to the field name token.
+ /// The payload is offset from the containing Decl AST node.
+ /// The source location points to the field name of:
+ /// * a field access expression (`a.b`), or
+ /// * the operand ("b" node) of a field initialization expression (`.a = b`)
/// The Decl is determined contextually.
node_offset_field_name: i32,
/// The source location points to the pointer of a pointer deref expression,
@@ -2114,6 +2215,7 @@ pub const LazySrcLoc = union(enum) {
.byte_offset,
.token_offset,
.node_offset,
+ .node_offset_back2tok,
.node_offset_var_decl_ty,
.node_offset_for_cond,
.node_offset_builtin_call_arg0,
@@ -2156,6 +2258,7 @@ pub const LazySrcLoc = union(enum) {
.byte_offset,
.token_offset,
.node_offset,
+ .node_offset_back2tok,
.node_offset_var_decl_ty,
.node_offset_for_cond,
.node_offset_builtin_call_arg0,
@@ -2189,6 +2292,20 @@ pub const InnerError = error{ OutOfMemory, AnalysisFail };
pub fn deinit(mod: *Module) void {
const gpa = mod.gpa;
+ // The callsite of `Compilation.create` owns the `root_pkg`, however
+ // Module owns the builtin and std packages that it adds.
+ if (mod.root_pkg.table.remove("builtin")) |entry| {
+ gpa.free(entry.key);
+ entry.value.destroy(gpa);
+ }
+ if (mod.root_pkg.table.remove("std")) |entry| {
+ gpa.free(entry.key);
+ entry.value.destroy(gpa);
+ }
+ if (mod.root_pkg.table.remove("root")) |entry| {
+ gpa.free(entry.key);
+ }
+
mod.compile_log_text.deinit(gpa);
mod.zig_cache_artifact_directory.handle.close();
@@ -2288,7 +2405,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void {
// We don't perform a deletion here, because this Decl or another one
// may end up referencing it before the update is complete.
dep.deletion_flag = true;
- try mod.deletion_set.append(mod.gpa, dep);
+ try mod.deletion_set.put(mod.gpa, dep, {});
}
}
decl.dependencies.clearRetainingCapacity();
@@ -2351,7 +2468,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
const tree = try mod.getAstTree(decl.container.file_scope);
const node_tags = tree.nodes.items(.tag);
const node_datas = tree.nodes.items(.data);
- const decl_node = tree.rootDecls()[decl.src_index];
+ const decl_node = decl.src_node;
switch (node_tags[decl_node]) {
.fn_decl => {
const fn_proto = node_datas[decl_node].lhs;
@@ -2406,6 +2523,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
const block_expr = node_datas[decl_node].lhs;
_ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr);
+ _ = try gen_scope.addBreak(.break_inline, 0, .void_value);
const code = try gen_scope.finish();
if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
@@ -3087,12 +3205,19 @@ fn astgenAndSemaVarDecl(
return type_changed;
}
-pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !void {
- try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.items().len + 1);
- try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.items().len + 1);
+/// Returns the depender's index of the dependee.
+pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !u32 {
+ try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.count() + 1);
+ try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.count() + 1);
+
+ if (dependee.deletion_flag) {
+ dependee.deletion_flag = false;
+ mod.deletion_set.removeAssertDiscard(dependee);
+ }
- depender.dependencies.putAssumeCapacity(dependee, {});
dependee.dependants.putAssumeCapacity(depender, {});
+ const gop = depender.dependencies.getOrPutAssumeCapacity(dependee);
+ return @intCast(u32, gop.index);
}
pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {
@@ -3117,17 +3242,19 @@ pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree {
var msg = std.ArrayList(u8).init(mod.gpa);
defer msg.deinit();
+ const token_starts = tree.tokens.items(.start);
+
try tree.renderError(parse_err, msg.writer());
const err_msg = try mod.gpa.create(ErrorMsg);
err_msg.* = .{
.src_loc = .{
.container = .{ .file_scope = root_scope },
- .lazy = .{ .token_abs = parse_err.token },
+ .lazy = .{ .byte_abs = token_starts[parse_err.token] },
},
.msg = msg.toOwnedSlice(),
};
- mod.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg);
+ mod.failed_files.putAssumeCapacityNoClobber(root_scope, err_msg);
root_scope.status = .unloaded_parse_failure;
return error.AnalysisFail;
}
@@ -3167,7 +3294,15 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
deleted_decls.putAssumeCapacityNoClobber(entry.key, {});
}
- for (decls) |decl_node, decl_i| switch (node_tags[decl_node]) {
+ // Keep track of decls that are invalidated from the update. Ultimately,
+ // the goal is to queue up `analyze_decl` tasks in the work queue for
+ // the outdated decls, but we cannot queue up the tasks until after
+ // we find out which ones have been deleted, otherwise there would be
+ // deleted Decl pointers in the work queue.
+ var outdated_decls = std.AutoArrayHashMap(*Decl, void).init(mod.gpa);
+ defer outdated_decls.deinit();
+
+ for (decls) |decl_node| switch (node_tags[decl_node]) {
.fn_decl => {
const fn_proto = node_datas[decl_node].lhs;
const body = node_datas[decl_node].rhs;
@@ -3177,8 +3312,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
body,
tree.fnProtoSimple(¶ms, fn_proto),
@@ -3187,8 +3322,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
.fn_proto_multi => try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
body,
tree.fnProtoMulti(fn_proto),
@@ -3198,8 +3333,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
body,
tree.fnProtoOne(¶ms, fn_proto),
@@ -3208,8 +3343,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
.fn_proto => try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
body,
tree.fnProto(fn_proto),
@@ -3222,8 +3357,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
0,
tree.fnProtoSimple(¶ms, decl_node),
@@ -3232,8 +3367,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
.fn_proto_multi => try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
0,
tree.fnProtoMulti(decl_node),
@@ -3243,8 +3378,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
0,
tree.fnProtoOne(¶ms, decl_node),
@@ -3253,8 +3388,8 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
.fn_proto => try mod.semaContainerFn(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
0,
tree.fnProto(decl_node),
@@ -3263,32 +3398,32 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
.global_var_decl => try mod.semaContainerVar(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
tree.globalVarDecl(decl_node),
),
.local_var_decl => try mod.semaContainerVar(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
tree.localVarDecl(decl_node),
),
.simple_var_decl => try mod.semaContainerVar(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
tree.simpleVarDecl(decl_node),
),
.aligned_var_decl => try mod.semaContainerVar(
container_scope,
&deleted_decls,
+ &outdated_decls,
decl_node,
- decl_i,
tree.*,
tree.alignedVarDecl(decl_node),
),
@@ -3301,49 +3436,48 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
const name_hash = container_scope.fullyQualifiedNameHash(name);
const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node));
- const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
+ const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash);
container_scope.decls.putAssumeCapacity(new_decl, {});
mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
},
- .container_field_init => try mod.semaContainerField(
- container_scope,
- &deleted_decls,
- decl_node,
- decl_i,
- tree.*,
- tree.containerFieldInit(decl_node),
- ),
- .container_field_align => try mod.semaContainerField(
- container_scope,
- &deleted_decls,
- decl_node,
- decl_i,
- tree.*,
- tree.containerFieldAlign(decl_node),
- ),
- .container_field => try mod.semaContainerField(
- container_scope,
- &deleted_decls,
- decl_node,
- decl_i,
- tree.*,
- tree.containerField(decl_node),
- ),
+ // Container fields are handled in AstGen.
+ .container_field_init,
+ .container_field_align,
+ .container_field,
+ => continue,
.test_decl => {
- log.err("TODO: analyze test decl", .{});
+ if (mod.comp.bin_file.options.is_test) {
+ log.err("TODO: analyze test decl", .{});
+ }
},
.@"usingnamespace" => {
log.err("TODO: analyze usingnamespace decl", .{});
},
else => unreachable,
};
- // Handle explicitly deleted decls from the source code. Not to be confused
- // with when we delete decls because they are no longer referenced.
+ // Handle explicitly deleted decls from the source code. This is one of two
+ // places that Decl deletions happen. The other is in `Compilation`, after
+ // `performAllTheWork`, where we iterate over `Module.deletion_set` and
+ // delete Decls which are no longer referenced.
+ // If a Decl is explicitly deleted from source, and also no longer referenced,
+ // it may be both in this `deleted_decls` set, as well as in the
+ // `Module.deletion_set`. To avoid deleting it twice, we remove it from the
+ // deletion set at this time.
for (deleted_decls.items()) |entry| {
- log.debug("noticed '{s}' deleted from source", .{entry.key.name});
- try mod.deleteDecl(entry.key);
+ const decl = entry.key;
+ log.debug("'{s}' deleted from source", .{decl.name});
+ if (decl.deletion_flag) {
+ log.debug("'{s}' redundantly in deletion set; removing", .{decl.name});
+ mod.deletion_set.removeAssertDiscard(decl);
+ }
+ try mod.deleteDecl(decl, &outdated_decls);
+ }
+ // Finally we can queue up re-analysis tasks after we have processed
+ // the deleted decls.
+ for (outdated_decls.items()) |entry| {
+ try mod.markOutdatedDecl(entry.key);
}
}
@@ -3351,8 +3485,8 @@ fn semaContainerFn(
mod: *Module,
container_scope: *Scope.Container,
deleted_decls: *std.AutoArrayHashMap(*Decl, void),
+ outdated_decls: *std.AutoArrayHashMap(*Decl, void),
decl_node: ast.Node.Index,
- decl_i: usize,
tree: ast.Tree,
body_node: ast.Node.Index,
fn_proto: ast.full.FnProto,
@@ -3361,28 +3495,34 @@ fn semaContainerFn(
defer tracy.end();
// We will create a Decl for it regardless of analysis status.
- const name_tok = fn_proto.name_token orelse {
+ const name_token = fn_proto.name_token orelse {
// This problem will go away with #1717.
@panic("TODO missing function name");
};
- const name = tree.tokenSlice(name_tok); // TODO use identifierTokenString
+ const name = tree.tokenSlice(name_token); // TODO use identifierTokenString
const name_hash = container_scope.fullyQualifiedNameHash(name);
const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node));
if (mod.decl_table.get(name_hash)) |decl| {
// Update the AST Node index of the decl, even if its contents are unchanged, it may
// have been re-ordered.
- decl.src_index = decl_i;
+ const prev_src_node = decl.src_node;
+ decl.src_node = decl_node;
if (deleted_decls.swapRemove(decl) == null) {
decl.analysis = .sema_failure;
const msg = try ErrorMsg.create(mod.gpa, .{
.container = .{ .file_scope = container_scope.file_scope },
- .lazy = .{ .token_abs = name_tok },
+ .lazy = .{ .token_abs = name_token },
}, "redefinition of '{s}'", .{decl.name});
errdefer msg.destroy(mod.gpa);
+ const other_src_loc: SrcLoc = .{
+ .container = .{ .file_scope = decl.container.file_scope },
+ .lazy = .{ .node_abs = prev_src_node },
+ };
+ try mod.errNoteNonLazy(other_src_loc, msg, "previous definition here", .{});
try mod.failed_decls.putNoClobber(mod.gpa, decl, msg);
} else {
if (!srcHashEql(decl.contents_hash, contents_hash)) {
- try mod.markOutdatedDecl(decl);
+ try outdated_decls.put(decl, {});
decl.contents_hash = contents_hash;
} else switch (mod.comp.bin_file.tag) {
.coff => {
@@ -3402,7 +3542,7 @@ fn semaContainerFn(
}
}
} else {
- const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
+ const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash);
container_scope.decls.putAssumeCapacity(new_decl, {});
if (fn_proto.extern_export_token) |maybe_export_token| {
const token_tags = tree.tokens.items(.tag);
@@ -3410,6 +3550,7 @@ fn semaContainerFn(
mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
}
}
+ new_decl.is_pub = fn_proto.visib_token != null;
}
}
@@ -3417,8 +3558,8 @@ fn semaContainerVar(
mod: *Module,
container_scope: *Scope.Container,
deleted_decls: *std.AutoArrayHashMap(*Decl, void),
+ outdated_decls: *std.AutoArrayHashMap(*Decl, void),
decl_node: ast.Node.Index,
- decl_i: usize,
tree: ast.Tree,
var_decl: ast.full.VarDecl,
) !void {
@@ -3432,21 +3573,27 @@ fn semaContainerVar(
if (mod.decl_table.get(name_hash)) |decl| {
// Update the AST Node index of the decl, even if its contents are unchanged, it may
// have been re-ordered.
- decl.src_index = decl_i;
+ const prev_src_node = decl.src_node;
+ decl.src_node = decl_node;
if (deleted_decls.swapRemove(decl) == null) {
decl.analysis = .sema_failure;
- const err_msg = try ErrorMsg.create(mod.gpa, .{
+ const msg = try ErrorMsg.create(mod.gpa, .{
.container = .{ .file_scope = container_scope.file_scope },
.lazy = .{ .token_abs = name_token },
}, "redefinition of '{s}'", .{decl.name});
- errdefer err_msg.destroy(mod.gpa);
- try mod.failed_decls.putNoClobber(mod.gpa, decl, err_msg);
+ errdefer msg.destroy(mod.gpa);
+ const other_src_loc: SrcLoc = .{
+ .container = .{ .file_scope = decl.container.file_scope },
+ .lazy = .{ .node_abs = prev_src_node },
+ };
+ try mod.errNoteNonLazy(other_src_loc, msg, "previous definition here", .{});
+ try mod.failed_decls.putNoClobber(mod.gpa, decl, msg);
} else if (!srcHashEql(decl.contents_hash, contents_hash)) {
- try mod.markOutdatedDecl(decl);
+ try outdated_decls.put(decl, {});
decl.contents_hash = contents_hash;
}
} else {
- const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_i, name_hash, contents_hash);
+ const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash);
container_scope.decls.putAssumeCapacity(new_decl, {});
if (var_decl.extern_export_token) |maybe_export_token| {
const token_tags = tree.tokens.items(.tag);
@@ -3454,35 +3601,31 @@ fn semaContainerVar(
mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
}
}
+ new_decl.is_pub = var_decl.visib_token != null;
}
}
-fn semaContainerField(
+pub fn deleteDecl(
mod: *Module,
- container_scope: *Scope.Container,
- deleted_decls: *std.AutoArrayHashMap(*Decl, void),
- decl_node: ast.Node.Index,
- decl_i: usize,
- tree: ast.Tree,
- field: ast.full.ContainerField,
+ decl: *Decl,
+ outdated_decls: ?*std.AutoArrayHashMap(*Decl, void),
) !void {
const tracy = trace(@src());
defer tracy.end();
- log.err("TODO: analyze container field", .{});
-}
-
-pub fn deleteDecl(mod: *Module, decl: *Decl) !void {
- const tracy = trace(@src());
- defer tracy.end();
+ log.debug("deleting decl '{s}'", .{decl.name});
- try mod.deletion_set.ensureCapacity(mod.gpa, mod.deletion_set.items.len + decl.dependencies.items().len);
+ if (outdated_decls) |map| {
+ _ = map.swapRemove(decl);
+ try map.ensureCapacity(map.count() + decl.dependants.count());
+ }
+ try mod.deletion_set.ensureCapacity(mod.gpa, mod.deletion_set.count() +
+ decl.dependencies.count());
// Remove from the namespace it resides in. In the case of an anonymous Decl it will
// not be present in the set, and this does nothing.
decl.container.removeDecl(decl);
- log.debug("deleting decl '{s}'", .{decl.name});
const name_hash = decl.fullyQualifiedNameHash();
mod.decl_table.removeAssertDiscard(name_hash);
// Remove itself from its dependencies, because we are about to destroy the decl pointer.
@@ -3493,16 +3636,22 @@ pub fn deleteDecl(mod: *Module, decl: *Decl) !void {
// We don't recursively perform a deletion here, because during the update,
// another reference to it may turn up.
dep.deletion_flag = true;
- mod.deletion_set.appendAssumeCapacity(dep);
+ mod.deletion_set.putAssumeCapacity(dep, {});
}
}
- // Anything that depends on this deleted decl certainly needs to be re-analyzed.
+ // Anything that depends on this deleted decl needs to be re-analyzed.
for (decl.dependants.items()) |entry| {
const dep = entry.key;
dep.removeDependency(decl);
- if (dep.analysis != .outdated) {
- // TODO Move this failure possibility to the top of the function.
- try mod.markOutdatedDecl(dep);
+ if (outdated_decls) |map| {
+ map.putAssumeCapacity(dep, {});
+ } else if (std.debug.runtime_safety) {
+ // If `outdated_decls` is `null`, it means we're being called from
+ // `Compilation` after `performAllTheWork` and we cannot queue up any
+ // more work. `dep` must necessarily be another Decl that is no longer
+ // being referenced, and will be in the `deletion_set`. Otherwise,
+ // something has gone wrong.
+ assert(mod.deletion_set.contains(dep));
}
}
if (mod.failed_decls.swapRemove(decl)) |entry| {
@@ -3638,7 +3787,7 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void {
fn allocateNewDecl(
mod: *Module,
scope: *Scope,
- src_index: usize,
+ src_node: ast.Node.Index,
contents_hash: std.zig.SrcHash,
) !*Decl {
// If we have emit-h then we must allocate a bigger structure to store the emit-h state.
@@ -3654,7 +3803,7 @@ fn allocateNewDecl(
new_decl.* = .{
.name = "",
.container = scope.namespace(),
- .src_index = src_index,
+ .src_node = src_node,
.typed_value = .{ .never_succeeded = {} },
.analysis = .unreferenced,
.deletion_flag = false,
@@ -3664,7 +3813,7 @@ fn allocateNewDecl(
.elf => .{ .elf = link.File.Elf.TextBlock.empty },
.macho => .{ .macho = link.File.MachO.TextBlock.empty },
.c => .{ .c = link.File.C.DeclBlock.empty },
- .wasm => .{ .wasm = {} },
+ .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
.spirv => .{ .spirv = {} },
},
.fn_link = switch (mod.comp.bin_file.tag) {
@@ -3672,7 +3821,7 @@ fn allocateNewDecl(
.elf => .{ .elf = link.File.Elf.SrcFn.empty },
.macho => .{ .macho = link.File.MachO.SrcFn.empty },
.c => .{ .c = link.File.C.FnBlock.empty },
- .wasm => .{ .wasm = null },
+ .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
.spirv => .{ .spirv = .{} },
},
.generation = 0,
@@ -3685,12 +3834,12 @@ fn createNewDecl(
mod: *Module,
scope: *Scope,
decl_name: []const u8,
- src_index: usize,
+ src_node: ast.Node.Index,
name_hash: Scope.NameHash,
contents_hash: std.zig.SrcHash,
) !*Decl {
try mod.decl_table.ensureCapacity(mod.gpa, mod.decl_table.items().len + 1);
- const new_decl = try mod.allocateNewDecl(scope, src_index, contents_hash);
+ const new_decl = try mod.allocateNewDecl(scope, src_node, contents_hash);
errdefer mod.gpa.destroy(new_decl);
new_decl.name = try mem.dupeZ(mod.gpa, u8, decl_name);
mod.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl);
@@ -3903,7 +4052,7 @@ pub fn createAnonymousDecl(
defer mod.gpa.free(name);
const name_hash = scope.namespace().fullyQualifiedNameHash(name);
const src_hash: std.zig.SrcHash = undefined;
- const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash);
+ const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash);
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
decl_arena_state.* = decl_arena.state;
@@ -3939,7 +4088,7 @@ pub fn createContainerDecl(
defer mod.gpa.free(name);
const name_hash = scope.namespace().fullyQualifiedNameHash(name);
const src_hash: std.zig.SrcHash = undefined;
- const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash);
+ const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash);
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
decl_arena_state.* = decl_arena.state;
@@ -4004,12 +4153,22 @@ pub fn errNote(
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!void {
+ return mod.errNoteNonLazy(src.toSrcLoc(scope), parent, format, args);
+}
+
+pub fn errNoteNonLazy(
+ mod: *Module,
+ src_loc: SrcLoc,
+ parent: *ErrorMsg,
+ comptime format: []const u8,
+ args: anytype,
+) error{OutOfMemory}!void {
const msg = try std.fmt.allocPrint(mod.gpa, format, args);
errdefer mod.gpa.free(msg);
parent.notes = try mod.gpa.realloc(parent.notes, parent.notes.len + 1);
parent.notes[parent.notes.len - 1] = .{
- .src_loc = src.toSrcLoc(scope),
+ .src_loc = src_loc,
.msg = msg,
};
}
@@ -4412,7 +4571,29 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex)
var buf: ArrayListUnmanaged(u8) = .{};
defer buf.deinit(mod.gpa);
try parseStrLit(mod, scope, token, &buf, ident_name, 1);
- return buf.toOwnedSlice(mod.gpa);
+ const duped = try scope.arena().dupe(u8, buf.items);
+ return duped;
+}
+
+/// `scope` is only used for error reporting.
+/// The string is stored in `arena` regardless of whether it uses @"" syntax.
+pub fn identifierTokenStringTreeArena(
+ mod: *Module,
+ scope: *Scope,
+ token: ast.TokenIndex,
+ tree: *const ast.Tree,
+ arena: *Allocator,
+) InnerError![]u8 {
+ const token_tags = tree.tokens.items(.tag);
+ assert(token_tags[token] == .identifier);
+ const ident_name = tree.tokenSlice(token);
+ if (!mem.startsWith(u8, ident_name, "@")) {
+ return arena.dupe(u8, ident_name);
+ }
+ var buf: ArrayListUnmanaged(u8) = .{};
+ defer buf.deinit(mod.gpa);
+ try parseStrLit(mod, scope, token, &buf, ident_name, 1);
+ return arena.dupe(u8, buf.items);
}
/// Given an identifier token, obtain the string for it (possibly parsing as a string
@@ -4502,3 +4683,10 @@ pub fn parseStrLit(
},
}
}
+
+pub fn unloadFile(mod: *Module, file_scope: *Scope.File) void {
+ if (file_scope.status == .unloaded_parse_failure) {
+ mod.failed_files.swapRemove(file_scope).?.value.destroy(mod.gpa);
+ }
+ file_scope.unload(mod.gpa);
+}
diff --git a/src/Package.zig b/src/Package.zig
@@ -4,18 +4,29 @@ const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const Allocator = mem.Allocator;
+const assert = std.debug.assert;
const Compilation = @import("Compilation.zig");
+const Module = @import("Module.zig");
pub const Table = std.StringHashMapUnmanaged(*Package);
+pub const root_namespace_hash: Module.Scope.NameHash = .{
+ 0, 0, 6, 6, 6, 0, 0, 0,
+ 6, 9, 0, 0, 0, 4, 2, 0,
+};
+
root_src_directory: Compilation.Directory,
/// Relative to `root_src_directory`. May contain path separators.
root_src_path: []const u8,
table: Table = .{},
parent: ?*Package = null,
+namespace_hash: Module.Scope.NameHash,
+/// Whether to free `root_src_directory` on `destroy`.
+root_src_directory_owned: bool = false,
/// Allocate a Package. No references to the slices passed are kept.
+/// Don't forget to set `namespace_hash` later.
pub fn create(
gpa: *Allocator,
/// Null indicates the current working directory
@@ -38,27 +49,69 @@ pub fn create(
.handle = if (owned_dir_path) |p| try fs.cwd().openDir(p, .{}) else fs.cwd(),
},
.root_src_path = owned_src_path,
+ .root_src_directory_owned = true,
+ .namespace_hash = undefined,
};
return ptr;
}
-/// Free all memory associated with this package and recursively call destroy
-/// on all packages in its table
+pub fn createWithDir(
+ gpa: *Allocator,
+ directory: Compilation.Directory,
+ /// Relative to `directory`. If null, means `directory` is the root src dir
+ /// and is owned externally.
+ root_src_dir_path: ?[]const u8,
+ /// Relative to root_src_dir_path
+ root_src_path: []const u8,
+) !*Package {
+ const ptr = try gpa.create(Package);
+ errdefer gpa.destroy(ptr);
+
+ const owned_src_path = try gpa.dupe(u8, root_src_path);
+ errdefer gpa.free(owned_src_path);
+
+ if (root_src_dir_path) |p| {
+ const owned_dir_path = try directory.join(gpa, &[1][]const u8{p});
+ errdefer gpa.free(owned_dir_path);
+
+ ptr.* = .{
+ .root_src_directory = .{
+ .path = owned_dir_path,
+ .handle = try directory.handle.openDir(p, .{}),
+ },
+ .root_src_directory_owned = true,
+ .root_src_path = owned_src_path,
+ .namespace_hash = undefined,
+ };
+ } else {
+ ptr.* = .{
+ .root_src_directory = directory,
+ .root_src_directory_owned = false,
+ .root_src_path = owned_src_path,
+ .namespace_hash = undefined,
+ };
+ }
+ return ptr;
+}
+
+/// Free all memory associated with this package. It does not destroy any packages
+/// inside its table; the caller is responsible for calling destroy() on them.
pub fn destroy(pkg: *Package, gpa: *Allocator) void {
gpa.free(pkg.root_src_path);
- // If root_src_directory.path is null then the handle is the cwd()
- // which shouldn't be closed.
- if (pkg.root_src_directory.path) |p| {
- gpa.free(p);
- pkg.root_src_directory.handle.close();
+ if (pkg.root_src_directory_owned) {
+ // If root_src_directory.path is null then the handle is the cwd()
+ // which shouldn't be closed.
+ if (pkg.root_src_directory.path) |p| {
+ gpa.free(p);
+ pkg.root_src_directory.handle.close();
+ }
}
{
var it = pkg.table.iterator();
while (it.next()) |kv| {
- kv.value.destroy(gpa);
gpa.free(kv.key);
}
}
@@ -72,3 +125,10 @@ pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package)
const name_dupe = try mem.dupe(gpa, u8, name);
pkg.table.putAssumeCapacityNoClobber(name_dupe, package);
}
+
+pub fn addAndAdopt(parent: *Package, gpa: *Allocator, name: []const u8, child: *Package) !void {
+ assert(child.parent == null); // make up your mind, who is the parent??
+ child.parent = parent;
+ child.namespace_hash = std.zig.hashName(parent.namespace_hash, ":", name);
+ return parent.add(gpa, name, child);
+}
diff --git a/src/Sema.zig b/src/Sema.zig
@@ -168,7 +168,6 @@ pub fn analyzeBody(
.cmp_lte => try sema.zirCmp(block, inst, .lte),
.cmp_neq => try sema.zirCmp(block, inst, .neq),
.coerce_result_ptr => try sema.zirCoerceResultPtr(block, inst),
- .@"const" => try sema.zirConst(block, inst),
.decl_ref => try sema.zirDeclRef(block, inst),
.decl_val => try sema.zirDeclVal(block, inst),
.load => try sema.zirLoad(block, inst),
@@ -179,6 +178,8 @@ pub fn analyzeBody(
.elem_val_node => try sema.zirElemValNode(block, inst),
.enum_literal => try sema.zirEnumLiteral(block, inst),
.enum_literal_small => try sema.zirEnumLiteralSmall(block, inst),
+ .enum_to_int => try sema.zirEnumToInt(block, inst),
+ .int_to_enum => try sema.zirIntToEnum(block, inst),
.err_union_code => try sema.zirErrUnionCode(block, inst),
.err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst),
.err_union_payload_safe => try sema.zirErrUnionPayload(block, inst, true),
@@ -198,9 +199,12 @@ pub fn analyzeBody(
.fn_type_cc => try sema.zirFnTypeCc(block, inst, false),
.fn_type_cc_var_args => try sema.zirFnTypeCc(block, inst, true),
.fn_type_var_args => try sema.zirFnType(block, inst, true),
+ .has_decl => try sema.zirHasDecl(block, inst),
.import => try sema.zirImport(block, inst),
.indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst),
.int => try sema.zirInt(block, inst),
+ .float => try sema.zirFloat(block, inst),
+ .float128 => try sema.zirFloat128(block, inst),
.int_type => try sema.zirIntType(block, inst),
.intcast => try sema.zirIntcast(block, inst),
.is_err => try sema.zirIsErr(block, inst),
@@ -255,16 +259,20 @@ pub fn analyzeBody(
.switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true),
.switch_capture_else => try sema.zirSwitchCaptureElse(block, inst, false),
.switch_capture_else_ref => try sema.zirSwitchCaptureElse(block, inst, true),
+ .type_info => try sema.zirTypeInfo(block, inst),
.typeof => try sema.zirTypeof(block, inst),
.typeof_elem => try sema.zirTypeofElem(block, inst),
.typeof_peer => try sema.zirTypeofPeer(block, inst),
.xor => try sema.zirBitwise(block, inst, .xor),
.struct_init_empty => try sema.zirStructInitEmpty(block, inst),
+ .struct_init => try sema.zirStructInit(block, inst),
+ .field_type => try sema.zirFieldType(block, inst),
.struct_decl => try sema.zirStructDecl(block, inst, .Auto),
.struct_decl_packed => try sema.zirStructDecl(block, inst, .Packed),
.struct_decl_extern => try sema.zirStructDecl(block, inst, .Extern),
- .enum_decl => try sema.zirEnumDecl(block, inst),
+ .enum_decl => try sema.zirEnumDecl(block, inst, false),
+ .enum_decl_nonexhaustive => try sema.zirEnumDecl(block, inst, true),
.union_decl => try sema.zirUnionDecl(block, inst),
.opaque_decl => try sema.zirOpaqueDecl(block, inst),
@@ -338,6 +346,10 @@ pub fn analyzeBody(
try sema.zirValidateStructInitPtr(block, inst);
continue;
},
+ .@"export" => {
+ try sema.zirExport(block, inst);
+ continue;
+ },
// Special case instructions to handle comptime control flow.
.repeat_inline => {
@@ -498,18 +510,6 @@ fn resolveInstConst(
};
}
-fn zirConst(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
- const tv_ptr = sema.code.instructions.items(.data)[inst].@"const";
- // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions
- // after analysis. This happens, for example, with variable declaration initialization
- // expressions.
- const typed_value_copy = try tv_ptr.copy(sema.arena);
- return sema.mod.constInst(sema.arena, .unneeded, typed_value_copy);
-}
-
fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
@@ -600,6 +600,11 @@ fn zirStructDecl(
const struct_obj = try new_decl_arena.allocator.create(Module.Struct);
const struct_ty = try Type.Tag.@"struct".create(&new_decl_arena.allocator, struct_obj);
+ const struct_val = try Value.Tag.ty.create(&new_decl_arena.allocator, struct_ty);
+ const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
+ .ty = Type.initTag(.type),
+ .val = struct_val,
+ });
struct_obj.* = .{
.owner_decl = sema.owner_decl,
.fields = fields_map,
@@ -607,16 +612,18 @@ fn zirStructDecl(
.container = .{
.ty = struct_ty,
.file_scope = block.getFileScope(),
+ .parent_name_hash = new_decl.fullyQualifiedNameHash(),
},
};
- const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
- .ty = Type.initTag(.type),
- .val = try Value.Tag.ty.create(gpa, struct_ty),
- });
return sema.analyzeDeclVal(block, src, new_decl);
}
-fn zirEnumDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+fn zirEnumDecl(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ nonexhaustive: bool,
+) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
@@ -787,8 +794,8 @@ fn zirAllocInferred(
const tracy = trace(@src());
defer tracy.end();
- const inst_data = sema.code.instructions.items(.data)[inst].un_node;
- const src = inst_data.src();
+ const src_node = sema.code.instructions.items(.data)[inst].node;
+ const src: LazySrcLoc = .{ .node_offset = src_node };
const val_payload = try sema.arena.create(Value.Payload.InferredAlloc);
val_payload.* = .{
@@ -837,12 +844,100 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Ind
const tracy = trace(@src());
defer tracy.end();
- const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
- const src = inst_data.src();
- const extra = sema.code.extraData(zir.Inst.Block, inst_data.payload_index);
- const instrs = sema.code.extra[extra.end..][0..extra.data.body_len];
+ const gpa = sema.gpa;
+ const mod = sema.mod;
+ const validate_inst = sema.code.instructions.items(.data)[inst].pl_node;
+ const struct_init_src = validate_inst.src();
+ const validate_extra = sema.code.extraData(zir.Inst.Block, validate_inst.payload_index);
+ const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len];
+
+ const struct_obj: *Module.Struct = s: {
+ const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
+ const field_ptr_extra = sema.code.extraData(zir.Inst.Field, field_ptr_data.payload_index).data;
+ const object_ptr = try sema.resolveInst(field_ptr_extra.lhs);
+ break :s object_ptr.ty.elemType().castTag(.@"struct").?.data;
+ };
+
+ // Maps field index to field_ptr index of where it was already initialized.
+ const found_fields = try gpa.alloc(zir.Inst.Index, struct_obj.fields.entries.items.len);
+ defer gpa.free(found_fields);
+
+ mem.set(zir.Inst.Index, found_fields, 0);
+
+ for (instrs) |field_ptr| {
+ const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
+ const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node };
+ const field_ptr_extra = sema.code.extraData(zir.Inst.Field, field_ptr_data.payload_index).data;
+ const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start);
+ const field_index = struct_obj.fields.getIndex(field_name) orelse
+ return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name);
+ if (found_fields[field_index] != 0) {
+ const other_field_ptr = found_fields[field_index];
+ const other_field_ptr_data = sema.code.instructions.items(.data)[other_field_ptr].pl_node;
+ const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_ptr_data.src_node };
+ const msg = msg: {
+ const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{});
+ errdefer msg.destroy(gpa);
+ try mod.errNote(&block.base, other_field_src, msg, "other field here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+ }
+ found_fields[field_index] = field_ptr;
+ }
+
+ var root_msg: ?*Module.ErrorMsg = null;
+
+ for (found_fields) |field_ptr, i| {
+ if (field_ptr != 0) continue;
- log.warn("TODO implement zirValidateStructInitPtr (compile errors for missing/dupe fields)", .{});
+ const field_name = struct_obj.fields.entries.items[i].key;
+ const template = "mising struct field: {s}";
+ const args = .{field_name};
+ if (root_msg) |msg| {
+ try mod.errNote(&block.base, struct_init_src, msg, template, args);
+ } else {
+ root_msg = try mod.errMsg(&block.base, struct_init_src, template, args);
+ }
+ }
+ if (root_msg) |msg| {
+ const fqn = try struct_obj.getFullyQualifiedName(gpa);
+ defer gpa.free(fqn);
+ try mod.errNoteNonLazy(
+ struct_obj.srcLoc(),
+ msg,
+ "struct '{s}' declared here",
+ .{fqn},
+ );
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+ }
+}
+
+fn failWithBadFieldAccess(
+ sema: *Sema,
+ block: *Scope.Block,
+ struct_obj: *Module.Struct,
+ field_src: LazySrcLoc,
+ field_name: []const u8,
+) InnerError {
+ const mod = sema.mod;
+ const gpa = sema.gpa;
+
+ const fqn = try struct_obj.getFullyQualifiedName(gpa);
+ defer gpa.free(fqn);
+
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ &block.base,
+ field_src,
+ "no field named '{s}' in struct '{s}'",
+ .{ field_name, fqn },
+ );
+ errdefer msg.destroy(gpa);
+ try mod.errNoteNonLazy(struct_obj.srcLoc(), msg, "struct declared here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
}
fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
@@ -981,6 +1076,31 @@ fn zirInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*In
return sema.mod.constIntUnsigned(sema.arena, .unneeded, Type.initTag(.comptime_int), int);
}
+fn zirFloat(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const arena = sema.arena;
+ const inst_data = sema.code.instructions.items(.data)[inst].float;
+ const src = inst_data.src();
+ const number = inst_data.number;
+
+ return sema.mod.constInst(arena, src, .{
+ .ty = Type.initTag(.comptime_float),
+ .val = try Value.Tag.float_32.create(arena, number),
+ });
+}
+
+fn zirFloat128(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const arena = sema.arena;
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Float128, inst_data.payload_index).data;
+ const src = inst_data.src();
+ const number = extra.get();
+
+ return sema.mod.constInst(arena, src, .{
+ .ty = Type.initTag(.comptime_float),
+ .val = try Value.Tag.float_128.create(arena, number),
+ });
+}
+
fn zirCompileError(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
const tracy = trace(@src());
defer tracy.end();
@@ -1222,6 +1342,28 @@ fn analyzeBlockBody(
return &merges.block_inst.base;
}
+fn zirExport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const src = inst_data.src();
+ const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+
+ // TODO (see corresponding TODO in AstGen) this is supposed to be a `decl_ref`
+ // instruction, which could reference any decl, which is then supposed to get
+ // exported, regardless of whether or not it is a function.
+ const target_fn = try sema.resolveInstConst(block, lhs_src, extra.lhs);
+ // TODO (see corresponding TODO in AstGen) this is supposed to be
+ // `std.builtin.ExportOptions`, not a string.
+ const export_name = try sema.resolveConstString(block, rhs_src, extra.rhs);
+
+ const actual_fn = target_fn.val.castTag(.function).?.data;
+ try sema.mod.analyzeExport(&block.base, src, export_name, actual_fn.owner_decl);
+}
+
fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -1291,22 +1433,16 @@ fn zirDbgStmtNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerE
}
fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
- const decl = sema.code.decls[inst_data.payload_index];
+ const decl = sema.owner_decl.dependencies.entries.items[inst_data.payload_index].key;
return sema.analyzeDeclRef(block, src, decl);
}
fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
- const tracy = trace(@src());
- defer tracy.end();
-
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
- const decl = sema.code.decls[inst_data.payload_index];
+ const decl = sema.owner_decl.dependencies.entries.items[inst_data.payload_index].key;
return sema.analyzeDeclVal(block, src, decl);
}
@@ -1763,6 +1899,143 @@ fn zirEnumLiteralSmall(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) I
});
}
+fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const mod = sema.mod;
+ const arena = sema.arena;
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand = try sema.resolveInst(inst_data.operand);
+
+ const enum_tag: *Inst = switch (operand.ty.zigTypeTag()) {
+ .Enum => operand,
+ .Union => {
+ //if (!operand.ty.unionHasTag()) {
+ // return mod.fail(
+ // &block.base,
+ // operand_src,
+ // "untagged union '{}' cannot be converted to integer",
+ // .{dest_ty_src},
+ // );
+ //}
+ return mod.fail(&block.base, operand_src, "TODO zirEnumToInt for tagged unions", .{});
+ },
+ else => {
+ return mod.fail(&block.base, operand_src, "expected enum or tagged union, found {}", .{
+ operand.ty,
+ });
+ },
+ };
+
+ var int_tag_type_buffer: Type.Payload.Bits = undefined;
+ const int_tag_ty = try enum_tag.ty.intTagType(&int_tag_type_buffer).copy(arena);
+
+ if (enum_tag.ty.onePossibleValue()) |opv| {
+ return mod.constInst(arena, src, .{
+ .ty = int_tag_ty,
+ .val = opv,
+ });
+ }
+
+ if (enum_tag.value()) |enum_tag_val| {
+ if (enum_tag_val.castTag(.enum_field_index)) |enum_field_payload| {
+ const field_index = enum_field_payload.data;
+ switch (enum_tag.ty.tag()) {
+ .enum_full => {
+ const enum_full = enum_tag.ty.castTag(.enum_full).?.data;
+ if (enum_full.values.count() != 0) {
+ const val = enum_full.values.entries.items[field_index].key;
+ return mod.constInst(arena, src, .{
+ .ty = int_tag_ty,
+ .val = val,
+ });
+ } else {
+ // Field index and integer values are the same.
+ const val = try Value.Tag.int_u64.create(arena, field_index);
+ return mod.constInst(arena, src, .{
+ .ty = int_tag_ty,
+ .val = val,
+ });
+ }
+ },
+ .enum_simple => {
+ // Field index and integer values are the same.
+ const val = try Value.Tag.int_u64.create(arena, field_index);
+ return mod.constInst(arena, src, .{
+ .ty = int_tag_ty,
+ .val = val,
+ });
+ },
+ else => unreachable,
+ }
+ } else {
+ // Assume it is already an integer and return it directly.
+ return mod.constInst(arena, src, .{
+ .ty = int_tag_ty,
+ .val = enum_tag_val,
+ });
+ }
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, int_tag_ty, .bitcast, enum_tag);
+}
+
+fn zirIntToEnum(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const mod = sema.mod;
+ const target = mod.getTarget();
+ const arena = sema.arena;
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const src = inst_data.src();
+ const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs);
+ const operand = try sema.resolveInst(extra.rhs);
+
+ if (dest_ty.zigTypeTag() != .Enum) {
+ return mod.fail(&block.base, dest_ty_src, "expected enum, found {}", .{dest_ty});
+ }
+
+ if (dest_ty.isNonexhaustiveEnum()) {
+ if (operand.value()) |int_val| {
+ return mod.constInst(arena, src, .{
+ .ty = dest_ty,
+ .val = int_val,
+ });
+ }
+ }
+
+ if (try sema.resolveDefinedValue(block, operand_src, operand)) |int_val| {
+ if (!dest_ty.enumHasInt(int_val, target)) {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ &block.base,
+ src,
+ "enum '{}' has no tag with value {}",
+ .{ dest_ty, int_val },
+ );
+ errdefer msg.destroy(sema.gpa);
+ try mod.errNoteNonLazy(
+ dest_ty.declSrcLoc(),
+ msg,
+ "enum declared here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+ }
+ return mod.constInst(arena, src, .{
+ .ty = dest_ty,
+ .val = int_val,
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return block.addUnOp(src, dest_ty, .bitcast, operand);
+}
+
/// Pointer in, pointer out.
fn zirOptionalPayloadPtr(
sema: *Sema,
@@ -2139,7 +2412,10 @@ fn zirFieldVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerErro
const extra = sema.code.extraData(zir.Inst.Field, inst_data.payload_index).data;
const field_name = sema.code.nullTerminatedString(extra.field_name_start);
const object = try sema.resolveInst(extra.lhs);
- const object_ptr = try sema.analyzeRef(block, src, object);
+ const object_ptr = if (object.ty.zigTypeTag() == .Pointer)
+ object
+ else
+ try sema.analyzeRef(block, src, object);
const result_ptr = try sema.namedFieldPtr(block, src, object_ptr, field_name, field_name_src);
return sema.analyzeLoad(block, src, result_ptr, result_ptr.src);
}
@@ -2292,7 +2568,10 @@ fn zirElemVal(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError
const bin_inst = sema.code.instructions.items(.data)[inst].bin;
const array = try sema.resolveInst(bin_inst.lhs);
- const array_ptr = try sema.analyzeRef(block, sema.src, array);
+ const array_ptr = if (array.ty.zigTypeTag() == .Pointer)
+ array
+ else
+ try sema.analyzeRef(block, sema.src, array);
const elem_index = try sema.resolveInst(bin_inst.rhs);
const result_ptr = try sema.elemPtr(block, sema.src, array_ptr, elem_index, sema.src);
return sema.analyzeLoad(block, sema.src, result_ptr, sema.src);
@@ -2307,7 +2586,10 @@ fn zirElemValNode(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerE
const elem_index_src: LazySrcLoc = .{ .node_offset_array_access_index = inst_data.src_node };
const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
const array = try sema.resolveInst(extra.lhs);
- const array_ptr = try sema.analyzeRef(block, src, array);
+ const array_ptr = if (array.ty.zigTypeTag() == .Pointer)
+ array
+ else
+ try sema.analyzeRef(block, src, array);
const elem_index = try sema.resolveInst(extra.rhs);
const result_ptr = try sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src);
return sema.analyzeLoad(block, src, result_ptr, src);
@@ -2492,6 +2774,8 @@ fn analyzeSwitch(
src_node_offset: i32,
) InnerError!*Inst {
const gpa = sema.gpa;
+ const mod = sema.mod;
+
const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) {
.none => .{ .body = &.{}, .end = extra_end },
.under, .@"else" => blk: {
@@ -2509,16 +2793,16 @@ fn analyzeSwitch(
const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset };
// Validate usage of '_' prongs.
- if (special_prong == .under and !operand.ty.isExhaustiveEnum()) {
+ if (special_prong == .under and !operand.ty.isNonexhaustiveEnum()) {
const msg = msg: {
- const msg = try sema.mod.errMsg(
+ const msg = try mod.errMsg(
&block.base,
src,
"'_' prong only allowed when switching on non-exhaustive enums",
.{},
);
errdefer msg.destroy(gpa);
- try sema.mod.errNote(
+ try mod.errNote(
&block.base,
special_prong_src,
msg,
@@ -2527,14 +2811,123 @@ fn analyzeSwitch(
);
break :msg msg;
};
- return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
}
// Validate for duplicate items, missing else prong, and invalid range.
switch (operand.ty.zigTypeTag()) {
- .Enum => return sema.mod.fail(&block.base, src, "TODO validate switch .Enum", .{}),
- .ErrorSet => return sema.mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}),
- .Union => return sema.mod.fail(&block.base, src, "TODO validate switch .Union", .{}),
+ .Enum => {
+ var seen_fields = try gpa.alloc(?AstGen.SwitchProngSrc, operand.ty.enumFieldCount());
+ defer gpa.free(seen_fields);
+
+ mem.set(?AstGen.SwitchProngSrc, seen_fields, null);
+
+ var extra_index: usize = special.end;
+ {
+ var scalar_i: u32 = 0;
+ while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
+ const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body = sema.code.extra[extra_index..][0..body_len];
+ extra_index += body_len;
+
+ try sema.validateSwitchItemEnum(
+ block,
+ seen_fields,
+ item_ref,
+ src_node_offset,
+ .{ .scalar = scalar_i },
+ );
+ }
+ }
+ {
+ var multi_i: u32 = 0;
+ while (multi_i < multi_cases_len) : (multi_i += 1) {
+ const items_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const ranges_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const body_len = sema.code.extra[extra_index];
+ extra_index += 1;
+ const items = sema.code.refSlice(extra_index, items_len);
+ extra_index += items_len + body_len;
+
+ for (items) |item_ref, item_i| {
+ try sema.validateSwitchItemEnum(
+ block,
+ seen_fields,
+ item_ref,
+ src_node_offset,
+ .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } },
+ );
+ }
+
+ try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset);
+ }
+ }
+ const all_tags_handled = for (seen_fields) |seen_src| {
+ if (seen_src == null) break false;
+ } else true;
+
+ switch (special_prong) {
+ .none => {
+ if (!all_tags_handled) {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ &block.base,
+ src,
+ "switch must handle all possibilities",
+ .{},
+ );
+ errdefer msg.destroy(sema.gpa);
+ for (seen_fields) |seen_src, i| {
+ if (seen_src != null) continue;
+
+ const field_name = operand.ty.enumFieldName(i);
+
+ // TODO have this point to the tag decl instead of here
+ try mod.errNote(
+ &block.base,
+ src,
+ msg,
+ "unhandled enumeration value: '{s}'",
+ .{field_name},
+ );
+ }
+ try mod.errNoteNonLazy(
+ operand.ty.declSrcLoc(),
+ msg,
+ "enum '{}' declared here",
+ .{operand.ty},
+ );
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+ }
+ },
+ .under => {
+ if (all_tags_handled) return mod.fail(
+ &block.base,
+ special_prong_src,
+ "unreachable '_' prong; all cases already handled",
+ .{},
+ );
+ },
+ .@"else" => {
+ if (all_tags_handled) return mod.fail(
+ &block.base,
+ special_prong_src,
+ "unreachable else prong; all cases already handled",
+ .{},
+ );
+ },
+ }
+ },
+
+ .ErrorSet => return mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}),
+ .Union => return mod.fail(&block.base, src, "TODO validate switch .Union", .{}),
.Int, .ComptimeInt => {
var range_set = RangeSet.init(gpa);
defer range_set.deinit();
@@ -2607,11 +3000,11 @@ fn analyzeSwitch(
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
- const min_int = try operand.ty.minInt(&arena, sema.mod.getTarget());
- const max_int = try operand.ty.maxInt(&arena, sema.mod.getTarget());
+ const min_int = try operand.ty.minInt(&arena, mod.getTarget());
+ const max_int = try operand.ty.maxInt(&arena, mod.getTarget());
if (try range_set.spans(min_int, max_int)) {
if (special_prong == .@"else") {
- return sema.mod.fail(
+ return mod.fail(
&block.base,
special_prong_src,
"unreachable else prong; all cases already handled",
@@ -2622,7 +3015,7 @@ fn analyzeSwitch(
}
}
if (special_prong != .@"else") {
- return sema.mod.fail(
+ return mod.fail(
&block.base,
src,
"switch must handle all possibilities",
@@ -2685,7 +3078,7 @@ fn analyzeSwitch(
switch (special_prong) {
.@"else" => {
if (true_count + false_count == 2) {
- return sema.mod.fail(
+ return mod.fail(
&block.base,
src,
"unreachable else prong; all cases already handled",
@@ -2695,7 +3088,7 @@ fn analyzeSwitch(
},
.under, .none => {
if (true_count + false_count < 2) {
- return sema.mod.fail(
+ return mod.fail(
&block.base,
src,
"switch must handle all possibilities",
@@ -2707,7 +3100,7 @@ fn analyzeSwitch(
},
.EnumLiteral, .Void, .Fn, .Pointer, .Type => {
if (special_prong != .@"else") {
- return sema.mod.fail(
+ return mod.fail(
&block.base,
src,
"else prong required when switching on type '{}'",
@@ -2779,7 +3172,7 @@ fn analyzeSwitch(
.AnyFrame,
.ComptimeFloat,
.Float,
- => return sema.mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{
+ => return mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{
operand.ty,
}),
}
@@ -3054,7 +3447,7 @@ fn resolveSwitchItemVal(
switch_node_offset: i32,
switch_prong_src: AstGen.SwitchProngSrc,
range_expand: AstGen.SwitchProngSrc.RangeExpand,
-) InnerError!Value {
+) InnerError!TypedValue {
const item = try sema.resolveInst(item_ref);
// We have to avoid the other helper functions here because we cannot construct a LazySrcLoc
// because we only have the switch AST node. Only if we know for sure we need to report
@@ -3064,7 +3457,7 @@ fn resolveSwitchItemVal(
const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand);
return sema.failWithUseOfUndef(block, src);
}
- return val;
+ return TypedValue{ .ty = item.ty, .val = val };
}
const src = switch_prong_src.resolve(block.src_decl, switch_node_offset, range_expand);
return sema.failWithNeededComptime(block, src);
@@ -3079,8 +3472,8 @@ fn validateSwitchRange(
src_node_offset: i32,
switch_prong_src: AstGen.SwitchProngSrc,
) InnerError!void {
- const first_val = try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first);
- const last_val = try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last);
+ const first_val = (try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first)).val;
+ const last_val = (try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last)).val;
const maybe_prev_src = try range_set.add(first_val, last_val, switch_prong_src);
return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
}
@@ -3093,11 +3486,46 @@ fn validateSwitchItem(
src_node_offset: i32,
switch_prong_src: AstGen.SwitchProngSrc,
) InnerError!void {
- const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
+ const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val;
const maybe_prev_src = try range_set.add(item_val, item_val, switch_prong_src);
return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
}
+fn validateSwitchItemEnum(
+ sema: *Sema,
+ block: *Scope.Block,
+ seen_fields: []?AstGen.SwitchProngSrc,
+ item_ref: zir.Inst.Ref,
+ src_node_offset: i32,
+ switch_prong_src: AstGen.SwitchProngSrc,
+) InnerError!void {
+ const mod = sema.mod;
+ const item_tv = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
+ const field_index = item_tv.ty.enumTagFieldIndex(item_tv.val) orelse {
+ const msg = msg: {
+ const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
+ const msg = try mod.errMsg(
+ &block.base,
+ src,
+ "enum '{}' has no tag with value '{}'",
+ .{ item_tv.ty, item_tv.val },
+ );
+ errdefer msg.destroy(sema.gpa);
+ try mod.errNoteNonLazy(
+ item_tv.ty.declSrcLoc(),
+ msg,
+ "enum declared here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+ };
+ const maybe_prev_src = seen_fields[field_index];
+ seen_fields[field_index] = switch_prong_src;
+ return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset);
+}
+
fn validateSwitchDupe(
sema: *Sema,
block: *Scope.Block,
@@ -3106,17 +3534,18 @@ fn validateSwitchDupe(
src_node_offset: i32,
) InnerError!void {
const prev_prong_src = maybe_prev_src orelse return;
+ const mod = sema.mod;
const src = switch_prong_src.resolve(block.src_decl, src_node_offset, .none);
const prev_src = prev_prong_src.resolve(block.src_decl, src_node_offset, .none);
const msg = msg: {
- const msg = try sema.mod.errMsg(
+ const msg = try mod.errMsg(
&block.base,
src,
"duplicate switch value",
.{},
);
errdefer msg.destroy(sema.gpa);
- try sema.mod.errNote(
+ try mod.errNote(
&block.base,
prev_src,
msg,
@@ -3125,7 +3554,7 @@ fn validateSwitchDupe(
);
break :msg msg;
};
- return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
}
fn validateSwitchItemBool(
@@ -3137,7 +3566,7 @@ fn validateSwitchItemBool(
src_node_offset: i32,
switch_prong_src: AstGen.SwitchProngSrc,
) InnerError!void {
- const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
+ const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val;
if (item_val.toBool()) {
true_count.* += 1;
} else {
@@ -3159,7 +3588,7 @@ fn validateSwitchItemSparse(
src_node_offset: i32,
switch_prong_src: AstGen.SwitchProngSrc,
) InnerError!void {
- const item_val = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none);
+ const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val;
const entry = (try seen_values.fetchPut(item_val, switch_prong_src)) orelse return;
return sema.validateSwitchDupe(block, entry.value, switch_prong_src, src_node_offset);
}
@@ -3197,6 +3626,34 @@ fn validateSwitchNoRange(
return sema.mod.failWithOwnedErrorMsg(&block.base, msg);
}
+fn zirHasDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
+ const src = inst_data.src();
+ const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const container_type = try sema.resolveType(block, lhs_src, extra.lhs);
+ const decl_name = try sema.resolveConstString(block, rhs_src, extra.rhs);
+ const mod = sema.mod;
+ const arena = sema.arena;
+
+ const container_scope = container_type.getContainerScope() orelse return mod.fail(
+ &block.base,
+ lhs_src,
+ "expected struct, enum, union, or opaque, found '{}'",
+ .{container_type},
+ );
+ if (mod.lookupDeclName(&container_scope.base, decl_name)) |decl| {
+ // TODO if !decl.is_pub and inDifferentFiles() return false
+ return mod.constBool(arena, src, true);
+ } else {
+ return mod.constBool(arena, src, false);
+ }
+}
+
fn zirImport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
@@ -3448,6 +3905,7 @@ fn analyzeArithmetic(
.subwrap => .subwrap,
.mul => .mul,
.mulwrap => .mulwrap,
+ .div => .div,
else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for operand '{s}''", .{@tagName(zir_tag)}),
};
@@ -3539,9 +3997,13 @@ fn zirCmp(
const tracy = trace(@src());
defer tracy.end();
+ const mod = sema.mod;
+
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const extra = sema.code.extraData(zir.Inst.Bin, inst_data.payload_index).data;
const src: LazySrcLoc = inst_data.src();
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
@@ -3553,7 +4015,7 @@ fn zirCmp(
const rhs_ty_tag = rhs.ty.zigTypeTag();
if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
// null == null, null != null
- return sema.mod.constBool(sema.arena, src, op == .eq);
+ return mod.constBool(sema.arena, src, op == .eq);
} else if (is_equality_cmp and
((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
@@ -3564,23 +4026,23 @@ fn zirCmp(
} else if (is_equality_cmp and
((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
{
- return sema.mod.fail(&block.base, src, "TODO implement C pointer cmp", .{});
+ return mod.fail(&block.base, src, "TODO implement C pointer cmp", .{});
} else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;
- return sema.mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});
+ return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});
} else if (is_equality_cmp and
((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
(rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
{
- return sema.mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
+ return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
} else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
if (!is_equality_cmp) {
- return sema.mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)});
+ return mod.fail(&block.base, src, "{s} operator not allowed for errors", .{@tagName(op)});
}
if (rhs.value()) |rval| {
if (lhs.value()) |lval| {
// TODO optimisation oppurtunity: evaluate if std.mem.eql is faster with the names, or calling to Module.getErrorValue to get the values and then compare them is faster
- return sema.mod.constBool(sema.arena, src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
+ return mod.constBool(sema.arena, src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
}
}
try sema.requireRuntimeBlock(block, src);
@@ -3592,11 +4054,36 @@ fn zirCmp(
return sema.cmpNumeric(block, src, lhs, rhs, op);
} else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
if (!is_equality_cmp) {
- return sema.mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)});
+ return mod.fail(&block.base, src, "{s} operator not allowed for types", .{@tagName(op)});
}
- return sema.mod.constBool(sema.arena, src, lhs.value().?.eql(rhs.value().?) == (op == .eq));
+ return mod.constBool(sema.arena, src, lhs.value().?.eql(rhs.value().?) == (op == .eq));
}
- return sema.mod.fail(&block.base, src, "TODO implement more cmp analysis", .{});
+
+ const instructions = &[_]*Inst{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions);
+ if (!resolved_type.isSelfComparable(is_equality_cmp)) {
+ return mod.fail(&block.base, src, "operator not allowed for type '{}'", .{resolved_type});
+ }
+
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+ try sema.requireRuntimeBlock(block, src); // TODO try to do it at comptime
+ const bool_type = Type.initTag(.bool); // TODO handle vectors
+ const tag: Inst.Tag = switch (op) {
+ .lt => .cmp_lt,
+ .lte => .cmp_lte,
+ .eq => .cmp_eq,
+ .gte => .cmp_gte,
+ .gt => .cmp_gt,
+ .neq => .cmp_neq,
+ };
+ return block.addBinOp(src, bool_type, tag, casted_lhs, casted_rhs);
+}
+
+fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const src = inst_data.src();
+ return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirTypeInfo", .{});
}
fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
@@ -3740,7 +4227,7 @@ fn zirBoolBr(
_ = try rhs_block.addBr(src, block_inst, rhs_result);
const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, then_block.instructions.items) };
- const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, rhs_block.instructions.items) };
+ const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, else_block.instructions.items) };
_ = try child_block.addCondBr(src, lhs, tzir_then_body, tzir_else_body);
block_inst.body = .{
@@ -4016,6 +4503,18 @@ fn zirStructInitEmpty(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) In
});
}
+fn zirStructInit(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ return sema.mod.fail(&block.base, src, "TODO: Sema.zirStructInit", .{});
+}
+
+fn zirFieldType(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ return sema.mod.fail(&block.base, src, "TODO: Sema.zirFieldType", .{});
+}
+
fn requireFunctionBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void {
if (sema.func == null) {
return sema.mod.fail(&block.base, src, "instruction illegal outside function body", .{});
@@ -4123,22 +4622,25 @@ fn namedFieldPtr(
field_name: []const u8,
field_name_src: LazySrcLoc,
) InnerError!*Inst {
+ const mod = sema.mod;
+ const arena = sema.arena;
+
const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
.Pointer => object_ptr.ty.elemType(),
- else => return sema.mod.fail(&block.base, object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
+ else => return mod.fail(&block.base, object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
};
switch (elem_ty.zigTypeTag()) {
.Array => {
if (mem.eql(u8, field_name, "len")) {
- return sema.mod.constInst(sema.arena, src, .{
+ return mod.constInst(arena, src, .{
.ty = Type.initTag(.single_const_pointer_to_comptime_int),
.val = try Value.Tag.ref_val.create(
- sema.arena,
- try Value.Tag.int_u64.create(sema.arena, elem_ty.arrayLen()),
+ arena,
+ try Value.Tag.int_u64.create(arena, elem_ty.arrayLen()),
),
});
} else {
- return sema.mod.fail(
+ return mod.fail(
&block.base,
field_name_src,
"no member named '{s}' in '{}'",
@@ -4151,15 +4653,15 @@ fn namedFieldPtr(
switch (ptr_child.zigTypeTag()) {
.Array => {
if (mem.eql(u8, field_name, "len")) {
- return sema.mod.constInst(sema.arena, src, .{
+ return mod.constInst(arena, src, .{
.ty = Type.initTag(.single_const_pointer_to_comptime_int),
.val = try Value.Tag.ref_val.create(
- sema.arena,
- try Value.Tag.int_u64.create(sema.arena, ptr_child.arrayLen()),
+ arena,
+ try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()),
),
});
} else {
- return sema.mod.fail(
+ return mod.fail(
&block.base,
field_name_src,
"no member named '{s}' in '{}'",
@@ -4174,7 +4676,7 @@ fn namedFieldPtr(
_ = try sema.resolveConstValue(block, object_ptr.src, object_ptr);
const result = try sema.analyzeLoad(block, src, object_ptr, object_ptr.src);
const val = result.value().?;
- const child_type = try val.toType(sema.arena);
+ const child_type = try val.toType(arena);
switch (child_type.zigTypeTag()) {
.ErrorSet => {
// TODO resolve inferred error sets
@@ -4188,42 +4690,92 @@ fn namedFieldPtr(
break :blk name;
}
}
- return sema.mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{
+ return mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{
field_name,
child_type,
});
- } else (try sema.mod.getErrorValue(field_name)).key;
+ } else (try mod.getErrorValue(field_name)).key;
- return sema.mod.constInst(sema.arena, src, .{
- .ty = try sema.mod.simplePtrType(sema.arena, child_type, false, .One),
+ return mod.constInst(arena, src, .{
+ .ty = try mod.simplePtrType(arena, child_type, false, .One),
.val = try Value.Tag.ref_val.create(
- sema.arena,
- try Value.Tag.@"error".create(sema.arena, .{
+ arena,
+ try Value.Tag.@"error".create(arena, .{
.name = name,
}),
),
});
},
- .Struct => {
- const container_scope = child_type.getContainerScope();
- if (sema.mod.lookupDeclName(&container_scope.base, field_name)) |decl| {
- // TODO if !decl.is_pub and inDifferentFiles() "{} is private"
- return sema.analyzeDeclRef(block, src, decl);
- }
+ .Struct, .Opaque, .Union => {
+ if (child_type.getContainerScope()) |container_scope| {
+ if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| {
+ if (!decl.is_pub and !(decl.container.file_scope == block.base.namespace().file_scope))
+ return mod.fail(&block.base, src, "'{s}' is private", .{field_name});
+ return sema.analyzeDeclRef(block, src, decl);
+ }
- if (container_scope.file_scope == sema.mod.root_scope) {
- return sema.mod.fail(&block.base, src, "root source file has no member called '{s}'", .{field_name});
- } else {
- return sema.mod.fail(&block.base, src, "container '{}' has no member called '{s}'", .{ child_type, field_name });
+ // TODO this will give false positives for structs inside the root file
+ if (container_scope.file_scope == mod.root_scope) {
+ return mod.fail(
+ &block.base,
+ src,
+ "root source file has no member named '{s}'",
+ .{field_name},
+ );
+ }
+ }
+ // TODO add note: declared here
+ const kw_name = switch (child_type.zigTypeTag()) {
+ .Struct => "struct",
+ .Opaque => "opaque",
+ .Union => "union",
+ else => unreachable,
+ };
+ return mod.fail(&block.base, src, "{s} '{}' has no member named '{s}'", .{
+ kw_name, child_type, field_name,
+ });
+ },
+ .Enum => {
+ if (child_type.getContainerScope()) |container_scope| {
+ if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| {
+ if (!decl.is_pub and !(decl.container.file_scope == block.base.namespace().file_scope))
+ return mod.fail(&block.base, src, "'{s}' is private", .{field_name});
+ return sema.analyzeDeclRef(block, src, decl);
+ }
}
+ const field_index = child_type.enumFieldIndex(field_name) orelse {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ &block.base,
+ src,
+ "enum '{}' has no member named '{s}'",
+ .{ child_type, field_name },
+ );
+ errdefer msg.destroy(sema.gpa);
+ try mod.errNoteNonLazy(
+ child_type.declSrcLoc(),
+ msg,
+ "enum declared here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+ };
+ const field_index_u32 = @intCast(u32, field_index);
+ const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32);
+ return mod.constInst(arena, src, .{
+ .ty = try mod.simplePtrType(arena, child_type, false, .One),
+ .val = try Value.Tag.ref_val.create(arena, enum_val),
+ });
},
- else => return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{child_type}),
+ else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}),
}
},
.Struct => return sema.analyzeStructFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty),
else => {},
}
- return sema.mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty});
+ return mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty});
}
fn analyzeStructFieldPtr(
@@ -4241,12 +4793,8 @@ fn analyzeStructFieldPtr(
const struct_obj = elem_ty.castTag(.@"struct").?.data;
- const field_index = struct_obj.fields.getIndex(field_name) orelse {
- // TODO note: struct S declared here
- return mod.fail(&block.base, field_name_src, "no field named '{s}' in struct '{}'", .{
- field_name, elem_ty,
- });
- };
+ const field_index = struct_obj.fields.getIndex(field_name) orelse
+ return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name);
const field = struct_obj.fields.entries.items[field_index].value;
const ptr_field_ty = try mod.simplePtrType(arena, field.ty, true, .One);
// TODO comptime field access
@@ -4262,37 +4810,51 @@ fn elemPtr(
elem_index: *Inst,
elem_index_src: LazySrcLoc,
) InnerError!*Inst {
- const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
+ const array_ty = switch (array_ptr.ty.zigTypeTag()) {
.Pointer => array_ptr.ty.elemType(),
else => return sema.mod.fail(&block.base, array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),
};
- if (!elem_ty.isIndexable()) {
- return sema.mod.fail(&block.base, src, "array access of non-array type '{}'", .{elem_ty});
+ if (!array_ty.isIndexable()) {
+ return sema.mod.fail(&block.base, src, "array access of non-array type '{}'", .{array_ty});
}
-
- if (elem_ty.isSinglePointer() and elem_ty.elemType().zigTypeTag() == .Array) {
+ if (array_ty.isSinglePointer() and array_ty.elemType().zigTypeTag() == .Array) {
// we have to deref the ptr operand to get the actual array pointer
const array_ptr_deref = try sema.analyzeLoad(block, src, array_ptr, array_ptr.src);
- if (array_ptr_deref.value()) |array_ptr_val| {
- if (elem_index.value()) |index_val| {
- // Both array pointer and index are compile-time known.
- const index_u64 = index_val.toUnsignedInt();
- // @intCast here because it would have been impossible to construct a value that
- // required a larger index.
- const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64));
- const pointee_type = elem_ty.elemType().elemType();
-
- return sema.mod.constInst(sema.arena, src, .{
- .ty = try Type.Tag.single_const_pointer.create(sema.arena, pointee_type),
- .val = elem_ptr,
- });
- }
- }
+ return sema.elemPtrArray(block, src, array_ptr_deref, elem_index, elem_index_src);
+ }
+ if (array_ty.zigTypeTag() == .Array) {
+ return sema.elemPtrArray(block, src, array_ptr, elem_index, elem_index_src);
}
return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr", .{});
}
+fn elemPtrArray(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ array_ptr: *Inst,
+ elem_index: *Inst,
+ elem_index_src: LazySrcLoc,
+) InnerError!*Inst {
+ if (array_ptr.value()) |array_ptr_val| {
+ if (elem_index.value()) |index_val| {
+ // Both array pointer and index are compile-time known.
+ const index_u64 = index_val.toUnsignedInt();
+ // @intCast here because it would have been impossible to construct a value that
+ // required a larger index.
+ const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64));
+ const pointee_type = array_ptr.ty.elemType().elemType();
+
+ return sema.mod.constInst(sema.arena, src, .{
+ .ty = try Type.Tag.single_const_pointer.create(sema.arena, pointee_type),
+ .val = elem_ptr,
+ });
+ }
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr for arrays", .{});
+}
+
fn coerce(
sema: *Sema,
block: *Scope.Block,
@@ -4312,10 +4874,13 @@ fn coerce(
return sema.bitcast(block, dest_type, inst);
}
+ const mod = sema.mod;
+ const arena = sema.arena;
+
// undefined to anything
if (inst.value()) |val| {
if (val.isUndef() or inst.ty.zigTypeTag() == .Undefined) {
- return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = val });
+ return mod.constInst(arena, inst_src, .{ .ty = dest_type, .val = val });
}
}
assert(inst.ty.zigTypeTag() != .Undefined);
@@ -4329,13 +4894,13 @@ fn coerce(
if (try sema.coerceNum(block, dest_type, inst)) |some|
return some;
- const target = sema.mod.getTarget();
+ const target = mod.getTarget();
switch (dest_type.zigTypeTag()) {
.Optional => {
// null to ?T
if (inst.ty.zigTypeTag() == .Null) {
- return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
+ return mod.constInst(arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
}
// T to ?T
@@ -4421,10 +4986,40 @@ fn coerce(
}
}
},
+ .Enum => {
+ // enum literal to enum
+ if (inst.ty.zigTypeTag() == .EnumLiteral) {
+ const val = try sema.resolveConstValue(block, inst_src, inst);
+ const bytes = val.castTag(.enum_literal).?.data;
+ const field_index = dest_type.enumFieldIndex(bytes) orelse {
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ &block.base,
+ inst_src,
+ "enum '{}' has no field named '{s}'",
+ .{ dest_type, bytes },
+ );
+ errdefer msg.destroy(sema.gpa);
+ try mod.errNoteNonLazy(
+ dest_type.declSrcLoc(),
+ msg,
+ "enum declared here",
+ .{},
+ );
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+ };
+ return mod.constInst(arena, inst_src, .{
+ .ty = dest_type,
+ .val = try Value.Tag.enum_field_index.create(arena, @intCast(u32, field_index)),
+ });
+ }
+ },
else => {},
}
- return sema.mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty });
+ return mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty });
}
const InMemoryCoercionResult = enum {
@@ -4542,7 +5137,7 @@ fn analyzeDeclVal(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl
}
fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) InnerError!*Inst {
- try sema.mod.declareDeclDependency(sema.owner_decl, decl);
+ _ = try sema.mod.declareDeclDependency(sema.owner_decl, decl);
sema.mod.ensureDeclAnalyzed(decl) catch |err| {
if (sema.func) |func| {
func.state = .dependency_failure;
@@ -4742,9 +5337,9 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin
try std.fs.path.resolve(sema.gpa, &[_][]const u8{ cur_pkg_dir_path, target_string });
errdefer sema.gpa.free(resolved_path);
- if (sema.mod.import_table.get(resolved_path)) |some| {
+ if (sema.mod.import_table.get(resolved_path)) |cached_import| {
sema.gpa.free(resolved_path);
- return some;
+ return cached_import;
}
if (found_pkg == null) {
@@ -4762,6 +5357,11 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin
const struct_ty = try Type.Tag.empty_struct.create(sema.gpa, &file_scope.root_container);
errdefer sema.gpa.destroy(struct_ty.castTag(.empty_struct).?);
+ const container_name_hash: Scope.NameHash = if (found_pkg) |pkg|
+ pkg.namespace_hash
+ else
+ std.zig.hashName(cur_pkg.namespace_hash, "/", resolved_path);
+
file_scope.* = .{
.sub_file_path = resolved_path,
.source = .{ .unloaded = {} },
@@ -4772,6 +5372,7 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin
.file_scope = file_scope,
.decls = .{},
.ty = struct_ty,
+ .parent_name_hash = container_name_hash,
},
};
sema.mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) {
diff --git a/src/clang.zig b/src/clang.zig
@@ -300,6 +300,14 @@ pub const ConstantExpr = opaque {};
pub const ContinueStmt = opaque {};
+pub const ConvertVectorExpr = opaque {
+ pub const getSrcExpr = ZigClangConvertVectorExpr_getSrcExpr;
+ extern fn ZigClangConvertVectorExpr_getSrcExpr(*const ConvertVectorExpr) *const Expr;
+
+ pub const getTypeSourceInfo_getType = ZigClangConvertVectorExpr_getTypeSourceInfo_getType;
+ extern fn ZigClangConvertVectorExpr_getTypeSourceInfo_getType(*const ConvertVectorExpr) QualType;
+};
+
pub const DecayedType = opaque {
pub const getDecayedType = ZigClangDecayedType_getDecayedType;
extern fn ZigClangDecayedType_getDecayedType(*const DecayedType) QualType;
@@ -748,6 +756,14 @@ pub const ReturnStmt = opaque {
extern fn ZigClangReturnStmt_getRetValue(*const ReturnStmt) ?*const Expr;
};
+pub const ShuffleVectorExpr = opaque {
+ pub const getNumSubExprs = ZigClangShuffleVectorExpr_getNumSubExprs;
+ extern fn ZigClangShuffleVectorExpr_getNumSubExprs(*const ShuffleVectorExpr) c_uint;
+
+ pub const getExpr = ZigClangShuffleVectorExpr_getExpr;
+ extern fn ZigClangShuffleVectorExpr_getExpr(*const ShuffleVectorExpr, c_uint) *const Expr;
+};
+
pub const SourceManager = opaque {
pub const getSpellingLoc = ZigClangSourceManager_getSpellingLoc;
extern fn ZigClangSourceManager_getSpellingLoc(*const SourceManager, Loc: SourceLocation) SourceLocation;
@@ -837,6 +853,9 @@ pub const Type = opaque {
pub const isRecordType = ZigClangType_isRecordType;
extern fn ZigClangType_isRecordType(*const Type) bool;
+ pub const isVectorType = ZigClangType_isVectorType;
+ extern fn ZigClangType_isVectorType(*const Type) bool;
+
pub const isIncompleteOrZeroLengthArrayType = ZigClangType_isIncompleteOrZeroLengthArrayType;
extern fn ZigClangType_isIncompleteOrZeroLengthArrayType(*const Type, *const ASTContext) bool;
@@ -937,6 +956,14 @@ pub const VarDecl = opaque {
extern fn ZigClangVarDecl_getTypeSourceInfo_getType(*const VarDecl) QualType;
};
+pub const VectorType = opaque {
+ pub const getElementType = ZigClangVectorType_getElementType;
+ extern fn ZigClangVectorType_getElementType(*const VectorType) QualType;
+
+ pub const getNumElements = ZigClangVectorType_getNumElements;
+ extern fn ZigClangVectorType_getNumElements(*const VectorType) c_uint;
+};
+
pub const WhileStmt = opaque {
pub const getCond = ZigClangWhileStmt_getCond;
extern fn ZigClangWhileStmt_getCond(*const WhileStmt) *const Expr;
diff --git a/src/codegen.zig b/src/codegen.zig
@@ -417,7 +417,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const node_datas = tree.nodes.items(.data);
const token_starts = tree.tokens.items(.start);
- const fn_decl = tree.rootDecls()[module_fn.owner_decl.src_index];
+ const fn_decl = module_fn.owner_decl.src_node;
assert(node_tags[fn_decl] == .fn_decl);
const block = node_datas[fn_decl].rhs;
const lbrace_src = token_starts[tree.firstToken(block)];
@@ -855,6 +855,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.not => return self.genNot(inst.castTag(.not).?),
.mul => return self.genMul(inst.castTag(.mul).?),
.mulwrap => return self.genMulWrap(inst.castTag(.mulwrap).?),
+ .div => return self.genDiv(inst.castTag(.div).?),
.ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?),
.ref => return self.genRef(inst.castTag(.ref).?),
.ret => return self.genRet(inst.castTag(.ret).?),
@@ -1092,6 +1093,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
}
+ fn genDiv(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
+ // No side effects, so if it's unreferenced, do nothing.
+ if (inst.base.isUnused())
+ return MCValue.dead;
+ switch (arch) {
+ else => return self.fail(inst.base.src, "TODO implement div for {}", .{self.target.cpu.arch}),
+ }
+ }
+
fn genBitAnd(self: *Self, inst: *ir.Inst.BinOp) !MCValue {
// No side effects, so if it's unreferenced, do nothing.
if (inst.base.isUnused())
@@ -1735,7 +1745,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (result) {
.register => |reg| {
- try self.register_manager.getRegAssumeFree(toCanonicalReg(reg), &inst.base);
+ try self.register_manager.registers.ensureCapacity(self.gpa, self.register_manager.registers.count() + 1);
+ self.register_manager.getRegAssumeFree(toCanonicalReg(reg), &inst.base);
},
else => {},
}
@@ -1783,8 +1794,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (mc_arg) {
.none => continue,
.register => |reg| {
+ try self.register_manager.getRegWithoutTracking(reg);
try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
- // TODO interact with the register allocator to mark the instruction as moved.
},
.stack_offset => {
// Here we need to emit instructions like this:
@@ -1925,8 +1936,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.compare_flags_signed => unreachable,
.compare_flags_unsigned => unreachable,
.register => |reg| {
+ try self.register_manager.getRegWithoutTracking(reg);
try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
- // TODO interact with the register allocator to mark the instruction as moved.
},
.stack_offset => {
return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
@@ -1988,8 +1999,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.compare_flags_signed => unreachable,
.compare_flags_unsigned => unreachable,
.register => |reg| {
+ try self.register_manager.getRegWithoutTracking(reg);
try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
- // TODO interact with the register allocator to mark the instruction as moved.
},
.stack_offset => {
return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
@@ -2039,8 +2050,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (mc_arg) {
.none => continue,
.register => |reg| {
+ try self.register_manager.getRegWithoutTracking(reg);
try self.genSetReg(arg.src, arg.ty, reg, arg_mcv);
- // TODO interact with the register allocator to mark the instruction as moved.
},
.stack_offset => {
// Here we need to emit instructions like this:
@@ -2704,8 +2715,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const reg_name = input[1 .. input.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
- const arg = try self.resolveInst(inst.args[i]);
- try self.genSetReg(inst.base.src, inst.args[i].ty, reg, arg);
+
+ const arg = inst.args[i];
+ const arg_mcv = try self.resolveInst(arg);
+ try self.register_manager.getRegWithoutTracking(reg);
+ try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
}
if (mem.eql(u8, inst.asm_source, "svc #0")) {
@@ -2734,8 +2748,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const reg_name = input[1 .. input.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
- const arg = try self.resolveInst(inst.args[i]);
- try self.genSetReg(inst.base.src, inst.args[i].ty, reg, arg);
+
+ const arg = inst.args[i];
+ const arg_mcv = try self.resolveInst(arg);
+ try self.register_manager.getRegWithoutTracking(reg);
+ try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
}
if (mem.eql(u8, inst.asm_source, "svc #0")) {
@@ -2766,8 +2783,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const reg_name = input[1 .. input.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
- const arg = try self.resolveInst(inst.args[i]);
- try self.genSetReg(inst.base.src, inst.args[i].ty, reg, arg);
+
+ const arg = inst.args[i];
+ const arg_mcv = try self.resolveInst(arg);
+ try self.register_manager.getRegWithoutTracking(reg);
+ try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
}
if (mem.eql(u8, inst.asm_source, "ecall")) {
@@ -2796,8 +2816,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
const reg_name = input[1 .. input.len - 1];
const reg = parseRegName(reg_name) orelse
return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name});
- const arg = try self.resolveInst(inst.args[i]);
- try self.genSetReg(inst.base.src, inst.args[i].ty, reg, arg);
+
+ const arg = inst.args[i];
+ const arg_mcv = try self.resolveInst(arg);
+ try self.register_manager.getRegWithoutTracking(reg);
+ try self.genSetReg(inst.base.src, arg.ty, reg, arg_mcv);
}
if (mem.eql(u8, inst.asm_source, "syscall")) {
@@ -3302,6 +3325,43 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .register = .{ .rn = reg } }).toU32());
}
},
+ .stack_offset => |unadjusted_off| {
+ // TODO: maybe addressing from sp instead of fp
+ const abi_size = ty.abiSize(self.target.*);
+ const adj_off = unadjusted_off + abi_size;
+
+ const rn: Register = switch (arch) {
+ .aarch64, .aarch64_be => .x29,
+ .aarch64_32 => .w29,
+ else => unreachable,
+ };
+
+ const offset = if (math.cast(i9, adj_off)) |imm|
+ Instruction.LoadStoreOffset.imm_post_index(-imm)
+ else |_|
+ Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off }));
+
+ switch (abi_size) {
+ 1, 2 => {
+ const ldr = switch (abi_size) {
+ 1 => Instruction.ldrb,
+ 2 => Instruction.ldrh,
+ else => unreachable, // unexpected abi size
+ };
+
+ writeInt(u32, try self.code.addManyAsArray(4), ldr(reg, rn, .{
+ .offset = offset,
+ }).toU32());
+ },
+ 4, 8 => {
+ writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .register = .{
+ .rn = rn,
+ .offset = offset,
+ } }).toU32());
+ },
+ else => return self.fail(src, "TODO implement genSetReg other types abi_size={}", .{abi_size}),
+ }
+ },
else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}),
},
.riscv64 => switch (mcv) {
diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig
@@ -200,7 +200,7 @@ test "FloatingPointRegister.toX" {
/// Represents an instruction in the AArch64 instruction set
pub const Instruction = union(enum) {
- MoveWideImmediate: packed struct {
+ move_wide_immediate: packed struct {
rd: u5,
imm16: u16,
hw: u2,
@@ -208,14 +208,14 @@ pub const Instruction = union(enum) {
opc: u2,
sf: u1,
},
- PCRelativeAddress: packed struct {
+ pc_relative_address: packed struct {
rd: u5,
immhi: u19,
fixed: u5 = 0b10000,
immlo: u2,
op: u1,
},
- LoadStoreRegister: packed struct {
+ load_store_register: packed struct {
rt: u5,
rn: u5,
offset: u12,
@@ -225,7 +225,7 @@ pub const Instruction = union(enum) {
fixed: u3 = 0b111,
size: u2,
},
- LoadStorePairOfRegisters: packed struct {
+ load_store_register_pair: packed struct {
rt1: u5,
rn: u5,
rt2: u5,
@@ -235,20 +235,20 @@ pub const Instruction = union(enum) {
fixed: u5 = 0b101_0_0,
opc: u2,
},
- LoadLiteral: packed struct {
+ load_literal: packed struct {
rt: u5,
imm19: u19,
fixed: u6 = 0b011_0_00,
opc: u2,
},
- ExceptionGeneration: packed struct {
+ exception_generation: packed struct {
ll: u2,
op2: u3,
imm16: u16,
opc: u3,
fixed: u8 = 0b1101_0100,
},
- UnconditionalBranchRegister: packed struct {
+ unconditional_branch_register: packed struct {
op4: u5,
rn: u5,
op3: u6,
@@ -256,15 +256,15 @@ pub const Instruction = union(enum) {
opc: u4,
fixed: u7 = 0b1101_011,
},
- UnconditionalBranchImmediate: packed struct {
+ unconditional_branch_immediate: packed struct {
imm26: u26,
fixed: u5 = 0b00101,
op: u1,
},
- NoOperation: packed struct {
+ no_operation: packed struct {
fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111,
},
- LogicalShiftedRegister: packed struct {
+ logical_shifted_register: packed struct {
rd: u5,
rn: u5,
imm6: u6,
@@ -275,7 +275,7 @@ pub const Instruction = union(enum) {
opc: u2,
sf: u1,
},
- AddSubtractImmediate: packed struct {
+ add_subtract_immediate: packed struct {
rd: u5,
rn: u5,
imm12: u12,
@@ -285,6 +285,20 @@ pub const Instruction = union(enum) {
op: u1,
sf: u1,
},
+ conditional_branch: struct {
+ cond: u4,
+ o0: u1,
+ imm19: u19,
+ o1: u1,
+ fixed: u7 = 0b0101010,
+ },
+ compare_and_branch: struct {
+ rt: u5,
+ imm19: u19,
+ op: u1,
+ fixed: u6 = 0b011010,
+ sf: u1,
+ },
pub const Shift = struct {
shift: Type = .lsl,
@@ -303,19 +317,73 @@ pub const Instruction = union(enum) {
};
};
+ pub const Condition = enum(u4) {
+ /// Integer: Equal
+ /// Floating point: Equal
+ eq,
+ /// Integer: Not equal
+ /// Floating point: Not equal or unordered
+ ne,
+ /// Integer: Carry set
+ /// Floating point: Greater than, equal, or unordered
+ cs,
+ /// Integer: Carry clear
+ /// Floating point: Less than
+ cc,
+ /// Integer: Minus, negative
+ /// Floating point: Less than
+ mi,
+ /// Integer: Plus, positive or zero
+ /// Floating point: Greater than, equal, or unordered
+ pl,
+ /// Integer: Overflow
+ /// Floating point: Unordered
+ vs,
+ /// Integer: No overflow
+ /// Floating point: Ordered
+ vc,
+ /// Integer: Unsigned higher
+ /// Floating point: Greater than, or unordered
+ hi,
+ /// Integer: Unsigned lower or same
+ /// Floating point: Less than or equal
+ ls,
+ /// Integer: Signed greater than or equal
+ /// Floating point: Greater than or equal
+ ge,
+ /// Integer: Signed less than
+ /// Floating point: Less than, or unordered
+ lt,
+ /// Integer: Signed greater than
+ /// Floating point: Greater than
+ gt,
+ /// Integer: Signed less than or equal
+ /// Floating point: Less than, equal, or unordered
+ le,
+ /// Integer: Always
+ /// Floating point: Always
+ al,
+ /// Integer: Always
+ /// Floating point: Always
+ nv,
+ };
+
pub fn toU32(self: Instruction) u32 {
return switch (self) {
- .MoveWideImmediate => |v| @bitCast(u32, v),
- .PCRelativeAddress => |v| @bitCast(u32, v),
- .LoadStoreRegister => |v| @bitCast(u32, v),
- .LoadStorePairOfRegisters => |v| @bitCast(u32, v),
- .LoadLiteral => |v| @bitCast(u32, v),
- .ExceptionGeneration => |v| @bitCast(u32, v),
- .UnconditionalBranchRegister => |v| @bitCast(u32, v),
- .UnconditionalBranchImmediate => |v| @bitCast(u32, v),
- .NoOperation => |v| @bitCast(u32, v),
- .LogicalShiftedRegister => |v| @bitCast(u32, v),
- .AddSubtractImmediate => |v| @bitCast(u32, v),
+ .move_wide_immediate => |v| @bitCast(u32, v),
+ .pc_relative_address => |v| @bitCast(u32, v),
+ .load_store_register => |v| @bitCast(u32, v),
+ .load_store_register_pair => |v| @bitCast(u32, v),
+ .load_literal => |v| @bitCast(u32, v),
+ .exception_generation => |v| @bitCast(u32, v),
+ .unconditional_branch_register => |v| @bitCast(u32, v),
+ .unconditional_branch_immediate => |v| @bitCast(u32, v),
+ .no_operation => |v| @bitCast(u32, v),
+ .logical_shifted_register => |v| @bitCast(u32, v),
+ .add_subtract_immediate => |v| @bitCast(u32, v),
+ // TODO once packed structs work, this can be refactored
+ .conditional_branch => |v| @as(u32, v.cond) | (@as(u32, v.o0) << 4) | (@as(u32, v.imm19) << 5) | (@as(u32, v.o1) << 24) | (@as(u32, v.fixed) << 25),
+ .compare_and_branch => |v| @as(u32, v.rt) | (@as(u32, v.imm19) << 5) | (@as(u32, v.op) << 24) | (@as(u32, v.fixed) << 25) | (@as(u32, v.sf) << 31),
};
}
@@ -329,7 +397,7 @@ pub const Instruction = union(enum) {
32 => {
assert(shift % 16 == 0 and shift <= 16);
return Instruction{
- .MoveWideImmediate = .{
+ .move_wide_immediate = .{
.rd = rd.id(),
.imm16 = imm16,
.hw = @intCast(u2, shift / 16),
@@ -341,7 +409,7 @@ pub const Instruction = union(enum) {
64 => {
assert(shift % 16 == 0 and shift <= 48);
return Instruction{
- .MoveWideImmediate = .{
+ .move_wide_immediate = .{
.rd = rd.id(),
.imm16 = imm16,
.hw = @intCast(u2, shift / 16),
@@ -358,7 +426,7 @@ pub const Instruction = union(enum) {
assert(rd.size() == 64);
const imm21_u = @bitCast(u21, imm21);
return Instruction{
- .PCRelativeAddress = .{
+ .pc_relative_address = .{
.rd = rd.id(),
.immlo = @truncate(u2, imm21_u),
.immhi = @truncate(u19, imm21_u >> 2),
@@ -487,11 +555,17 @@ pub const Instruction = union(enum) {
/// Which kind of load/store to perform
const LoadStoreVariant = enum {
/// 32-bit or 64-bit
- normal,
- /// 16-bit
- half,
- /// 8-bit
- byte,
+ str,
+ /// 16-bit, zero-extended
+ strh,
+ /// 8-bit, zero-extended
+ strb,
+ /// 32-bit or 64-bit
+ ldr,
+ /// 16-bit, zero-extended
+ ldrh,
+ /// 8-bit, zero-extended
+ ldrb,
};
fn loadStoreRegister(
@@ -499,7 +573,6 @@ pub const Instruction = union(enum) {
rn: Register,
offset: LoadStoreOffset,
variant: LoadStoreVariant,
- load: bool,
) Instruction {
const off = offset.toU12();
const op1: u2 = blk: {
@@ -512,9 +585,12 @@ pub const Instruction = union(enum) {
}
break :blk 0b00;
};
- const opc: u2 = if (load) 0b01 else 0b00;
+ const opc: u2 = switch (variant) {
+ .ldr, .ldrh, .ldrb => 0b01,
+ .str, .strh, .strb => 0b00,
+ };
return Instruction{
- .LoadStoreRegister = .{
+ .load_store_register = .{
.rt = rt.id(),
.rn = rn.id(),
.offset = off,
@@ -523,20 +599,20 @@ pub const Instruction = union(enum) {
.v = 0,
.size = blk: {
switch (variant) {
- .normal => switch (rt.size()) {
+ .ldr, .str => switch (rt.size()) {
32 => break :blk 0b10,
64 => break :blk 0b11,
else => unreachable, // unexpected register size
},
- .half => break :blk 0b01,
- .byte => break :blk 0b00,
+ .ldrh, .strh => break :blk 0b01,
+ .ldrb, .strb => break :blk 0b00,
}
},
},
};
}
- fn loadStorePairOfRegisters(
+ fn loadStoreRegisterPair(
rt1: Register,
rt2: Register,
rn: Register,
@@ -549,7 +625,7 @@ pub const Instruction = union(enum) {
assert(-256 <= offset and offset <= 252);
const imm7 = @truncate(u7, @bitCast(u9, offset >> 2));
return Instruction{
- .LoadStorePairOfRegisters = .{
+ .load_store_register_pair = .{
.rt1 = rt1.id(),
.rn = rn.id(),
.rt2 = rt2.id(),
@@ -564,7 +640,7 @@ pub const Instruction = union(enum) {
assert(-512 <= offset and offset <= 504);
const imm7 = @truncate(u7, @bitCast(u9, offset >> 3));
return Instruction{
- .LoadStorePairOfRegisters = .{
+ .load_store_register_pair = .{
.rt1 = rt1.id(),
.rn = rn.id(),
.rt2 = rt2.id(),
@@ -583,7 +659,7 @@ pub const Instruction = union(enum) {
switch (rt.size()) {
32 => {
return Instruction{
- .LoadLiteral = .{
+ .load_literal = .{
.rt = rt.id(),
.imm19 = imm19,
.opc = 0b00,
@@ -592,7 +668,7 @@ pub const Instruction = union(enum) {
},
64 => {
return Instruction{
- .LoadLiteral = .{
+ .load_literal = .{
.rt = rt.id(),
.imm19 = imm19,
.opc = 0b01,
@@ -610,7 +686,7 @@ pub const Instruction = union(enum) {
imm16: u16,
) Instruction {
return Instruction{
- .ExceptionGeneration = .{
+ .exception_generation = .{
.ll = ll,
.op2 = op2,
.imm16 = imm16,
@@ -629,7 +705,7 @@ pub const Instruction = union(enum) {
assert(rn.size() == 64);
return Instruction{
- .UnconditionalBranchRegister = .{
+ .unconditional_branch_register = .{
.op4 = op4,
.rn = rn.id(),
.op3 = op3,
@@ -644,7 +720,7 @@ pub const Instruction = union(enum) {
offset: i28,
) Instruction {
return Instruction{
- .UnconditionalBranchImmediate = .{
+ .unconditional_branch_immediate = .{
.imm26 = @bitCast(u26, @intCast(i26, offset >> 2)),
.op = op,
},
@@ -663,7 +739,7 @@ pub const Instruction = union(enum) {
32 => {
assert(shift.amount < 32);
return Instruction{
- .LogicalShiftedRegister = .{
+ .logical_shifted_register = .{
.rd = rd.id(),
.rn = rn.id(),
.imm6 = shift.amount,
@@ -677,7 +753,7 @@ pub const Instruction = union(enum) {
},
64 => {
return Instruction{
- .LogicalShiftedRegister = .{
+ .logical_shifted_register = .{
.rd = rd.id(),
.rn = rn.id(),
.imm6 = shift.amount,
@@ -702,7 +778,7 @@ pub const Instruction = union(enum) {
shift: bool,
) Instruction {
return Instruction{
- .AddSubtractImmediate = .{
+ .add_subtract_immediate = .{
.rd = rd.id(),
.rn = rn.id(),
.imm12 = imm12,
@@ -718,6 +794,43 @@ pub const Instruction = union(enum) {
};
}
+ fn conditionalBranch(
+ o0: u1,
+ o1: u1,
+ cond: Condition,
+ offset: i21,
+ ) Instruction {
+ assert(offset & 0b11 == 0b00);
+ return Instruction{
+ .conditional_branch = .{
+ .cond = @enumToInt(cond),
+ .o0 = o0,
+ .imm19 = @bitCast(u19, @intCast(i19, offset >> 2)),
+ .o1 = o1,
+ },
+ };
+ }
+
+ fn compareAndBranch(
+ op: u1,
+ rt: Register,
+ offset: i21,
+ ) Instruction {
+ assert(offset & 0b11 == 0b00);
+ return Instruction{
+ .compare_and_branch = .{
+ .rt = rt.id(),
+ .imm19 = @bitCast(u19, @intCast(i19, offset >> 2)),
+ .op = op,
+ .sf = switch (rt.size()) {
+ 32 => 0b0,
+ 64 => 0b1,
+ else => unreachable, // unexpected register size
+ },
+ },
+ };
+ }
+
// Helper functions for assembly syntax functions
// Move wide (immediate)
@@ -756,25 +869,33 @@ pub const Instruction = union(enum) {
pub fn ldr(rt: Register, args: LdrArgs) Instruction {
switch (args) {
- .register => |info| return loadStoreRegister(rt, info.rn, info.offset, .normal, true),
+ .register => |info| return loadStoreRegister(rt, info.rn, info.offset, .ldr),
.literal => |literal| return loadLiteral(rt, literal),
}
}
+ pub fn ldrh(rt: Register, rn: Register, args: StrArgs) Instruction {
+ return loadStoreRegister(rt, rn, args.offset, .ldrh);
+ }
+
+ pub fn ldrb(rt: Register, rn: Register, args: StrArgs) Instruction {
+ return loadStoreRegister(rt, rn, args.offset, .ldrb);
+ }
+
pub const StrArgs = struct {
offset: LoadStoreOffset = LoadStoreOffset.none,
};
pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction {
- return loadStoreRegister(rt, rn, args.offset, .normal, false);
+ return loadStoreRegister(rt, rn, args.offset, .str);
}
pub fn strh(rt: Register, rn: Register, args: StrArgs) Instruction {
- return loadStoreRegister(rt, rn, args.offset, .half, false);
+ return loadStoreRegister(rt, rn, args.offset, .strh);
}
pub fn strb(rt: Register, rn: Register, args: StrArgs) Instruction {
- return loadStoreRegister(rt, rn, args.offset, .byte, false);
+ return loadStoreRegister(rt, rn, args.offset, .strb);
}
// Load or store pair of registers
@@ -805,19 +926,19 @@ pub const Instruction = union(enum) {
};
pub fn ldp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction {
- return loadStorePairOfRegisters(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), true);
+ return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), true);
}
pub fn ldnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
- return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, true);
+ return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, true);
}
pub fn stp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction {
- return loadStorePairOfRegisters(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), false);
+ return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), false);
}
pub fn stnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
- return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, false);
+ return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, false);
}
// Exception generation
@@ -869,7 +990,7 @@ pub const Instruction = union(enum) {
// Nop
pub fn nop() Instruction {
- return Instruction{ .NoOperation = .{} };
+ return Instruction{ .no_operation = .{} };
}
// Logical (shifted register)
@@ -923,6 +1044,22 @@ pub const Instruction = union(enum) {
pub fn subs(rd: Register, rn: Register, imm: u12, shift: bool) Instruction {
return addSubtractImmediate(0b1, 0b1, rd, rn, imm, shift);
}
+
+ // Conditional branch
+
+ pub fn bCond(cond: Condition, offset: i21) Instruction {
+ return conditionalBranch(0b0, 0b0, cond, offset);
+ }
+
+ // Compare and branch
+
+ pub fn cbz(rt: Register, offset: i21) Instruction {
+ return compareAndBranch(0b0, rt, offset);
+ }
+
+ pub fn cbnz(rt: Register, offset: i21) Instruction {
+ return compareAndBranch(0b1, rt, offset);
+ }
};
test {
@@ -1004,6 +1141,14 @@ test "serialize instructions" {
.inst = Instruction.ldr(.x2, .{ .literal = 0x1 }),
.expected = 0b01_011_0_00_0000000000000000001_00010,
},
+ .{ // ldrh x7, [x4], #0xaa
+ .inst = Instruction.ldrh(.x7, .x4, .{ .offset = Instruction.LoadStoreOffset.imm_post_index(0xaa) }),
+ .expected = 0b01_111_0_00_01_0_010101010_01_00100_00111,
+ },
+ .{ // ldrb x9, [x15, #0xff]!
+ .inst = Instruction.ldrb(.x9, .x15, .{ .offset = Instruction.LoadStoreOffset.imm_pre_index(0xff) }),
+ .expected = 0b00_111_0_00_01_0_011111111_11_01111_01001,
+ },
.{ // str x2, [x1]
.inst = Instruction.str(.x2, .x1, .{}),
.expected = 0b11_111_0_01_00_000000000000_00001_00010,
@@ -1068,6 +1213,14 @@ test "serialize instructions" {
.inst = Instruction.subs(.x0, .x5, 11, true),
.expected = 0b1_1_1_100010_1_0000_0000_1011_00101_00000,
},
+ .{ // b.hi #-4
+ .inst = Instruction.bCond(.hi, -4),
+ .expected = 0b0101010_0_1111111111111111111_0_1000,
+ },
+ .{ // cbz x10, #40
+ .inst = Instruction.cbz(.x10, 40),
+ .expected = 0b1_011010_0_0000000000000001010_01010,
+ },
};
for (testcases) |case| {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
@@ -44,22 +44,34 @@ fn formatTypeAsCIdentifier(
var buffer = [1]u8{0} ** 128;
// We don't care if it gets cut off, it's still more unique than a number
var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer;
+ return formatIdent(buf, "", .{}, writer);
+}
+
+pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) {
+ return .{ .data = t };
+}
- for (buf) |c, i| {
+fn formatIdent(
+ ident: []const u8,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+) !void {
+ for (ident) |c, i| {
switch (c) {
- 0 => return writer.writeAll(buf[0..i]),
- 'a'...'z', 'A'...'Z', '_', '$' => {},
+ 'a'...'z', 'A'...'Z', '_' => try writer.writeByte(c),
'0'...'9' => if (i == 0) {
- buf[i] = '_';
+ try writer.print("${x:2}", .{c});
+ } else {
+ try writer.writeByte(c);
},
- else => buf[i] = '_',
+ else => try writer.print("${x:2}", .{c}),
}
}
- return writer.writeAll(buf);
}
-pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) {
- return .{ .data = t };
+pub fn fmtIdent(ident: []const u8) std.fmt.Formatter(formatIdent) {
+ return .{ .data = ident };
}
/// This data is available when outputting .c code for a Module.
@@ -160,7 +172,10 @@ pub const DeclGen = struct {
val: Value,
) error{ OutOfMemory, AnalysisFail }!void {
if (val.isUndef()) {
- return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{});
+ // This should lower to 0xaa bytes in safe modes, and for unsafe modes should
+ // lower to leaving variables uninitialized (that might need to be implemented
+ // outside of this function).
+ return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement renderValue undef", .{});
}
switch (t.zigTypeTag()) {
.Int => {
@@ -276,6 +291,31 @@ pub const DeclGen = struct {
try writer.writeAll(", .error = 0 }");
}
},
+ .Enum => {
+ switch (val.tag()) {
+ .enum_field_index => {
+ const field_index = val.castTag(.enum_field_index).?.data;
+ switch (t.tag()) {
+ .enum_simple => return writer.print("{d}", .{field_index}),
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = t.cast(Type.Payload.EnumFull).?.data;
+ if (enum_full.values.count() != 0) {
+ const tag_val = enum_full.values.entries.items[field_index].key;
+ return dg.renderValue(writer, enum_full.tag_ty, tag_val);
+ } else {
+ return writer.print("{d}", .{field_index});
+ }
+ },
+ else => unreachable,
+ }
+ },
+ else => {
+ var int_tag_ty_buffer: Type.Payload.Bits = undefined;
+ const int_tag_ty = t.intTagType(&int_tag_ty_buffer);
+ return dg.renderValue(writer, int_tag_ty, val);
+ },
+ }
+ },
else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement value {s}", .{
@tagName(e),
}),
@@ -356,6 +396,9 @@ pub const DeclGen = struct {
else => unreachable,
}
},
+
+ .Float => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Float", .{}),
+
.Pointer => {
if (t.isSlice()) {
return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement slices", .{});
@@ -430,10 +473,59 @@ pub const DeclGen = struct {
try w.writeAll(name);
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
},
- .Null, .Undefined => unreachable, // must be const or comptime
- else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type {s}", .{
- @tagName(e),
- }),
+ .Struct => {
+ if (dg.typedefs.get(t)) |some| {
+ return w.writeAll(some.name);
+ }
+ const struct_obj = t.castTag(.@"struct").?.data; // Handle 0 bit types elsewhere.
+ const fqn = try struct_obj.getFullyQualifiedName(dg.typedefs.allocator);
+ defer dg.typedefs.allocator.free(fqn);
+
+ var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
+ defer buffer.deinit();
+
+ try buffer.appendSlice("typedef struct {\n");
+ for (struct_obj.fields.entries.items) |entry| {
+ try buffer.append(' ');
+ try dg.renderType(buffer.writer(), entry.value.ty);
+ try buffer.writer().print(" {s};\n", .{fmtIdent(entry.key)});
+ }
+ try buffer.appendSlice("} ");
+
+ const name_start = buffer.items.len;
+ try buffer.writer().print("zig_S_{s};\n", .{fmtIdent(fqn)});
+
+ const rendered = buffer.toOwnedSlice();
+ errdefer dg.typedefs.allocator.free(rendered);
+ const name = rendered[name_start .. rendered.len - 2];
+
+ try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
+ try w.writeAll(name);
+ dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
+ },
+ .Enum => {
+ // For enums, we simply use the integer tag type.
+ var int_tag_ty_buffer: Type.Payload.Bits = undefined;
+ const int_tag_ty = t.intTagType(&int_tag_ty_buffer);
+
+ try dg.renderType(w, int_tag_ty);
+ },
+ .Union => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Union", .{}),
+ .Fn => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Fn", .{}),
+ .Opaque => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Opaque", .{}),
+ .Frame => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Frame", .{}),
+ .AnyFrame => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type AnyFrame", .{}),
+ .Vector => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Vector", .{}),
+
+ .Null,
+ .Undefined,
+ .EnumLiteral,
+ .ComptimeFloat,
+ .ComptimeInt,
+ .Type,
+ => unreachable, // must be const or comptime
+
+ .BoundFn => unreachable, // this type will be deleted from the language
}
}
@@ -525,8 +617,26 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
for (body.instructions) |inst| {
const result_value = switch (inst.tag) {
- .constant => unreachable, // excluded from function bodies
+ // TODO use a different strategy for add that communicates to the optimizer
+ // that wrapping is UB.
.add => try genBinOp(o, inst.castTag(.add).?, " + "),
+ // TODO make this do wrapping arithmetic for signed ints
+ .addwrap => try genBinOp(o, inst.castTag(.add).?, " + "),
+ // TODO use a different strategy for sub that communicates to the optimizer
+ // that wrapping is UB.
+ .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
+ // TODO make this do wrapping arithmetic for signed ints
+ .subwrap => try genBinOp(o, inst.castTag(.sub).?, " - "),
+ // TODO use a different strategy for mul that communicates to the optimizer
+ // that wrapping is UB.
+ .mul => try genBinOp(o, inst.castTag(.sub).?, " * "),
+ // TODO make this do wrapping multiplication for signed ints
+ .mulwrap => try genBinOp(o, inst.castTag(.sub).?, " * "),
+ // TODO use a different strategy for div that communicates to the optimizer
+ // that wrapping is UB.
+ .div => try genBinOp(o, inst.castTag(.div).?, " / "),
+
+ .constant => unreachable, // excluded from function bodies
.alloc => try genAlloc(o, inst.castTag(.alloc).?),
.arg => genArg(o),
.assembly => try genAsm(o, inst.castTag(.assembly).?),
@@ -546,7 +656,6 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
.ret => try genRet(o, inst.castTag(.ret).?),
.retvoid => try genRetVoid(o),
.store => try genStore(o, inst.castTag(.store).?),
- .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
.unreach => try genUnreach(o, inst.castTag(.unreach).?),
.loop => try genLoop(o, inst.castTag(.loop).?),
.condbr => try genCondBr(o, inst.castTag(.condbr).?),
@@ -567,17 +676,24 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
.wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?),
.optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?),
.optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?),
+ .ref => try genRef(o, inst.castTag(.ref).?),
+ .struct_field_ptr => try genStructFieldPtr(o, inst.castTag(.struct_field_ptr).?),
+
.is_err => try genIsErr(o, inst.castTag(.is_err).?),
.is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?),
.error_to_int => try genErrorToInt(o, inst.castTag(.error_to_int).?),
.int_to_error => try genIntToError(o, inst.castTag(.int_to_error).?),
+
.unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?),
.unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?),
.unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?),
.unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?),
.wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?),
.wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?),
- else => |e| return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for {}", .{e}),
+ .br_block_flat => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for br_block_flat", .{}),
+ .ptrtoint => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for ptrtoint", .{}),
+ .varptr => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for varptr", .{}),
+ .floatcast => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for floatcast", .{}),
};
switch (result_value) {
.none => {},
@@ -996,6 +1112,37 @@ fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue {
return local;
}
+fn genRef(o: *Object, inst: *Inst.UnOp) !CValue {
+ const writer = o.writer();
+ const operand = try o.resolveInst(inst.operand);
+
+ const local = try o.allocLocal(inst.base.ty, .Const);
+ try writer.writeAll(" = ");
+ try o.writeCValue(writer, operand);
+ try writer.writeAll(";\n");
+ return local;
+}
+
+fn genStructFieldPtr(o: *Object, inst: *Inst.StructFieldPtr) !CValue {
+ const writer = o.writer();
+ const struct_ptr = try o.resolveInst(inst.struct_ptr);
+ const struct_obj = inst.struct_ptr.ty.elemType().castTag(.@"struct").?.data;
+ const field_name = struct_obj.fields.entries.items[inst.field_index].key;
+
+ const local = try o.allocLocal(inst.base.ty, .Const);
+ switch (struct_ptr) {
+ .local_ref => |i| {
+ try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) });
+ },
+ else => {
+ try writer.writeAll(" = &");
+ try o.writeCValue(writer, struct_ptr);
+ try writer.print("->{};\n", .{fmtIdent(field_name)});
+ },
+ }
+ return local;
+}
+
// *(E!T) -> E NOT *E
fn genUnwrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
const writer = o.writer();
@@ -1088,7 +1235,7 @@ fn IndentWriter(comptime UnderlyingWriter: type) type {
pub const Error = UnderlyingWriter.Error;
pub const Writer = std.io.Writer(*Self, Error, write);
- pub const indent_delta = 4;
+ pub const indent_delta = 1;
underlying_writer: UnderlyingWriter,
indent_count: usize = 0,
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
@@ -76,7 +76,6 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
.spirv32 => return error.LLVMBackendDoesNotSupportSPIRV,
.spirv64 => return error.LLVMBackendDoesNotSupportSPIRV,
};
- // TODO Add a sub-arch for some architectures depending on CPU features.
const llvm_os = switch (target.os.tag) {
.freestanding => "unknown",
diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig
@@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const assert = std.debug.assert;
+const testing = std.testing;
const leb = std.leb;
const mem = std.mem;
const wasm = std.wasm;
@@ -15,9 +16,12 @@ const Value = @import("../value.zig").Value;
const Compilation = @import("../Compilation.zig");
const AnyMCValue = @import("../codegen.zig").AnyMCValue;
const LazySrcLoc = Module.LazySrcLoc;
+const link = @import("../link.zig");
+const TypedValue = @import("../TypedValue.zig");
/// Wasm Value, created when generating an instruction
const WValue = union(enum) {
+ /// May be referenced but is unused
none: void,
/// Index of the local variable
local: u32,
@@ -29,6 +33,450 @@ const WValue = union(enum) {
block_idx: u32,
};
+/// Wasm ops, but without input/output/signedness information
+/// Used for `buildOpcode`
+const Op = enum {
+ @"unreachable",
+ nop,
+ block,
+ loop,
+ @"if",
+ @"else",
+ end,
+ br,
+ br_if,
+ br_table,
+ @"return",
+ call,
+ call_indirect,
+ drop,
+ select,
+ local_get,
+ local_set,
+ local_tee,
+ global_get,
+ global_set,
+ load,
+ store,
+ memory_size,
+ memory_grow,
+ @"const",
+ eqz,
+ eq,
+ ne,
+ lt,
+ gt,
+ le,
+ ge,
+ clz,
+ ctz,
+ popcnt,
+ add,
+ sub,
+ mul,
+ div,
+ rem,
+ @"and",
+ @"or",
+ xor,
+ shl,
+ shr,
+ rotl,
+ rotr,
+ abs,
+ neg,
+ ceil,
+ floor,
+ trunc,
+ nearest,
+ sqrt,
+ min,
+ max,
+ copysign,
+ wrap,
+ convert,
+ demote,
+ promote,
+ reinterpret,
+ extend,
+};
+
+/// Contains the settings needed to create an `Opcode` using `buildOpcode`.
+///
+/// The fields correspond to the opcode name. Here is an example
+/// i32_trunc_f32_s
+/// ^ ^ ^ ^
+/// | | | |
+/// valtype1 | | |
+/// = .i32 | | |
+/// | | |
+/// op | |
+/// = .trunc | |
+/// | |
+/// valtype2 |
+/// = .f32 |
+/// |
+/// width |
+/// = null |
+/// |
+/// signed
+/// = true
+///
+/// There can be missing fields, here are some more examples:
+/// i64_load8_u
+/// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
+/// i32_mul
+/// --> .{ .valtype1 = .i32, .op = .trunc }
+/// nop
+/// --> .{ .op = .nop }
+const OpcodeBuildArguments = struct {
+ /// First valtype in the opcode (usually represents the type of the output)
+ valtype1: ?wasm.Valtype = null,
+ /// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
+ op: Op,
+ /// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
+ width: ?u8 = null,
+ /// Second valtype in the opcode name (usually represents the type of the input)
+ valtype2: ?wasm.Valtype = null,
+ /// Signedness of the op
+ signedness: ?std.builtin.Signedness = null,
+};
+
+/// Helper function that builds an Opcode given the arguments needed
+fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
+ switch (args.op) {
+ .@"unreachable" => return .@"unreachable",
+ .nop => return .nop,
+ .block => return .block,
+ .loop => return .loop,
+ .@"if" => return .@"if",
+ .@"else" => return .@"else",
+ .end => return .end,
+ .br => return .br,
+ .br_if => return .br_if,
+ .br_table => return .br_table,
+ .@"return" => return .@"return",
+ .call => return .call,
+ .call_indirect => return .call_indirect,
+ .drop => return .drop,
+ .select => return .select,
+ .local_get => return .local_get,
+ .local_set => return .local_set,
+ .local_tee => return .local_tee,
+ .global_get => return .global_get,
+ .global_set => return .global_set,
+
+ .load => if (args.width) |width| switch (width) {
+ 8 => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
+ .f32, .f64 => unreachable,
+ },
+ 16 => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
+ .f32, .f64 => unreachable,
+ },
+ 32 => switch (args.valtype1.?) {
+ .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
+ .i32, .f32, .f64 => unreachable,
+ },
+ else => unreachable,
+ } else switch (args.valtype1.?) {
+ .i32 => return .i32_load,
+ .i64 => return .i64_load,
+ .f32 => return .f32_load,
+ .f64 => return .f64_load,
+ },
+ .store => if (args.width) |width| {
+ switch (width) {
+ 8 => switch (args.valtype1.?) {
+ .i32 => return .i32_store8,
+ .i64 => return .i64_store8,
+ .f32, .f64 => unreachable,
+ },
+ 16 => switch (args.valtype1.?) {
+ .i32 => return .i32_store16,
+ .i64 => return .i64_store16,
+ .f32, .f64 => unreachable,
+ },
+ 32 => switch (args.valtype1.?) {
+ .i64 => return .i64_store32,
+ .i32, .f32, .f64 => unreachable,
+ },
+ else => unreachable,
+ }
+ } else {
+ switch (args.valtype1.?) {
+ .i32 => return .i32_store,
+ .i64 => return .i64_store,
+ .f32 => return .f32_store,
+ .f64 => return .f64_store,
+ }
+ },
+
+ .memory_size => return .memory_size,
+ .memory_grow => return .memory_grow,
+
+ .@"const" => switch (args.valtype1.?) {
+ .i32 => return .i32_const,
+ .i64 => return .i64_const,
+ .f32 => return .f32_const,
+ .f64 => return .f64_const,
+ },
+
+ .eqz => switch (args.valtype1.?) {
+ .i32 => return .i32_eqz,
+ .i64 => return .i64_eqz,
+ .f32, .f64 => unreachable,
+ },
+ .eq => switch (args.valtype1.?) {
+ .i32 => return .i32_eq,
+ .i64 => return .i64_eq,
+ .f32 => return .f32_eq,
+ .f64 => return .f64_eq,
+ },
+ .ne => switch (args.valtype1.?) {
+ .i32 => return .i32_ne,
+ .i64 => return .i64_ne,
+ .f32 => return .f32_ne,
+ .f64 => return .f64_ne,
+ },
+
+ .lt => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u,
+ .f32 => return .f32_lt,
+ .f64 => return .f64_lt,
+ },
+ .gt => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u,
+ .f32 => return .f32_gt,
+ .f64 => return .f64_gt,
+ },
+ .le => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u,
+ .f32 => return .f32_le,
+ .f64 => return .f64_le,
+ },
+ .ge => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u,
+ .f32 => return .f32_ge,
+ .f64 => return .f64_ge,
+ },
+
+ .clz => switch (args.valtype1.?) {
+ .i32 => return .i32_clz,
+ .i64 => return .i64_clz,
+ .f32, .f64 => unreachable,
+ },
+ .ctz => switch (args.valtype1.?) {
+ .i32 => return .i32_ctz,
+ .i64 => return .i64_ctz,
+ .f32, .f64 => unreachable,
+ },
+ .popcnt => switch (args.valtype1.?) {
+ .i32 => return .i32_popcnt,
+ .i64 => return .i64_popcnt,
+ .f32, .f64 => unreachable,
+ },
+
+ .add => switch (args.valtype1.?) {
+ .i32 => return .i32_add,
+ .i64 => return .i64_add,
+ .f32 => return .f32_add,
+ .f64 => return .f64_add,
+ },
+ .sub => switch (args.valtype1.?) {
+ .i32 => return .i32_sub,
+ .i64 => return .i64_sub,
+ .f32 => return .f32_sub,
+ .f64 => return .f64_sub,
+ },
+ .mul => switch (args.valtype1.?) {
+ .i32 => return .i32_mul,
+ .i64 => return .i64_mul,
+ .f32 => return .f32_mul,
+ .f64 => return .f64_mul,
+ },
+
+ .div => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u,
+ .f32 => return .f32_div,
+ .f64 => return .f64_div,
+ },
+ .rem => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u,
+ .f32, .f64 => unreachable,
+ },
+
+ .@"and" => switch (args.valtype1.?) {
+ .i32 => return .i32_and,
+ .i64 => return .i64_and,
+ .f32, .f64 => unreachable,
+ },
+ .@"or" => switch (args.valtype1.?) {
+ .i32 => return .i32_or,
+ .i64 => return .i64_or,
+ .f32, .f64 => unreachable,
+ },
+ .xor => switch (args.valtype1.?) {
+ .i32 => return .i32_xor,
+ .i64 => return .i64_xor,
+ .f32, .f64 => unreachable,
+ },
+
+ .shl => switch (args.valtype1.?) {
+ .i32 => return .i32_shl,
+ .i64 => return .i64_shl,
+ .f32, .f64 => unreachable,
+ },
+ .shr => switch (args.valtype1.?) {
+ .i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u,
+ .i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u,
+ .f32, .f64 => unreachable,
+ },
+ .rotl => switch (args.valtype1.?) {
+ .i32 => return .i32_rotl,
+ .i64 => return .i64_rotl,
+ .f32, .f64 => unreachable,
+ },
+ .rotr => switch (args.valtype1.?) {
+ .i32 => return .i32_rotr,
+ .i64 => return .i64_rotr,
+ .f32, .f64 => unreachable,
+ },
+
+ .abs => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_abs,
+ .f64 => return .f64_abs,
+ },
+ .neg => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_neg,
+ .f64 => return .f64_neg,
+ },
+ .ceil => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_ceil,
+ .f64 => return .f64_ceil,
+ },
+ .floor => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_floor,
+ .f64 => return .f64_floor,
+ },
+ .trunc => switch (args.valtype1.?) {
+ .i32 => switch (args.valtype2.?) {
+ .i32 => unreachable,
+ .i64 => unreachable,
+ .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
+ .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
+ },
+ .i64 => unreachable,
+ .f32 => return .f32_trunc,
+ .f64 => return .f64_trunc,
+ },
+ .nearest => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_nearest,
+ .f64 => return .f64_nearest,
+ },
+ .sqrt => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_sqrt,
+ .f64 => return .f64_sqrt,
+ },
+ .min => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_min,
+ .f64 => return .f64_min,
+ },
+ .max => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_max,
+ .f64 => return .f64_max,
+ },
+ .copysign => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => return .f32_copysign,
+ .f64 => return .f64_copysign,
+ },
+
+ .wrap => switch (args.valtype1.?) {
+ .i32 => switch (args.valtype2.?) {
+ .i32 => unreachable,
+ .i64 => return .i32_wrap_i64,
+ .f32, .f64 => unreachable,
+ },
+ .i64, .f32, .f64 => unreachable,
+ },
+ .convert => switch (args.valtype1.?) {
+ .i32, .i64 => unreachable,
+ .f32 => switch (args.valtype2.?) {
+ .i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u,
+ .i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u,
+ .f32, .f64 => unreachable,
+ },
+ .f64 => switch (args.valtype2.?) {
+ .i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u,
+ .i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u,
+ .f32, .f64 => unreachable,
+ },
+ },
+ .demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable,
+ .promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable,
+ .reinterpret => switch (args.valtype1.?) {
+ .i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable,
+ .i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable,
+ .f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable,
+ .f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable,
+ },
+ .extend => switch (args.valtype1.?) {
+ .i32 => switch (args.width.?) {
+ 8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable,
+ 16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable,
+ else => unreachable,
+ },
+ .i64 => switch (args.width.?) {
+ 8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable,
+ 16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable,
+ 32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable,
+ else => unreachable,
+ },
+ .f32, .f64 => unreachable,
+ },
+ }
+}
+
+test "Wasm - buildOpcode" {
+ // Make sure buildOpcode is referenced, and test some examples
+ const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 });
+ const end = buildOpcode(.{ .op = .end });
+ const local_get = buildOpcode(.{ .op = .local_get });
+ const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
+ const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
+
+ testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
+ testing.expectEqual(@as(wasm.Opcode, .end), end);
+ testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
+ testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
+ testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
+}
+
+pub const Result = union(enum) {
+ /// The codegen bytes have been appended to `Context.code`
+ appended: void,
+ /// The data is managed externally and are part of the `Result`
+ externally_managed: []const u8,
+};
+
/// Hashmap to store generated `WValue` for each `Inst`
pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
@@ -58,10 +506,14 @@ pub const Context = struct {
/// List of all locals' types generated throughout this declaration
/// used to emit locals count at start of 'code' section.
locals: std.ArrayListUnmanaged(u8),
+ /// The Target we're emitting (used to call intInfo)
+ target: std.Target,
const InnerError = error{
OutOfMemory,
CodegenFail,
+ /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
+ AnalysisFail,
};
pub fn deinit(self: *Context) void {
@@ -89,17 +541,31 @@ pub const Context = struct {
return self.values.get(inst).?; // Instruction does not dominate all uses!
}
- /// Using a given `Type`, returns the corresponding wasm value type
- fn genValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
- return switch (ty.tag()) {
- .f32 => wasm.valtype(.f32),
- .f64 => wasm.valtype(.f64),
- .u32, .i32, .bool => wasm.valtype(.i32),
- .u64, .i64 => wasm.valtype(.i64),
- else => self.fail(src, "TODO - Wasm genValtype for type '{s}'", .{ty.tag()}),
+ /// Using a given `Type`, returns the corresponding wasm Valtype
+ fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {
+ return switch (ty.zigTypeTag()) {
+ .Float => blk: {
+ const bits = ty.floatBits(self.target);
+ if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
+ if (bits == 64) break :blk wasm.Valtype.f64;
+ return self.fail(src, "Float bit size not supported by wasm: '{d}'", .{bits});
+ },
+ .Int => blk: {
+ const info = ty.intInfo(self.target);
+ if (info.bits <= 32) break :blk wasm.Valtype.i32;
+ if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
+ return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
+ },
+ .Bool, .Pointer => wasm.Valtype.i32,
+ else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),
};
}
+ /// Using a given `Type`, returns the byte representation of its wasm value type
+ fn genValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
+ return wasm.valtype(try self.typeToValtype(src, ty));
+ }
+
/// Using a given `Type`, returns the corresponding wasm value type
/// Differently from `genValtype` this also allows `void` to create a block
/// with no return type
@@ -157,59 +623,97 @@ pub const Context = struct {
}
/// Generates the wasm bytecode for the function declaration belonging to `Context`
- pub fn gen(self: *Context) InnerError!void {
- assert(self.code.items.len == 0);
- try self.genFunctype();
-
- // Write instructions
- // TODO: check for and handle death of instructions
- const tv = self.decl.typed_value.most_recent.typed_value;
- const mod_fn = blk: {
- if (tv.val.castTag(.function)) |func| break :blk func.data;
- if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions
- return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
- };
-
- // Reserve space to write the size after generating the code as well as space for locals count
- try self.code.resize(10);
-
- try self.genBody(mod_fn.body);
-
- // finally, write our local types at the 'offset' position
- {
- leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
-
- // offset into 'code' section where we will put our locals types
- var local_offset: usize = 10;
-
- // emit the actual locals amount
- for (self.locals.items) |local| {
- var buf: [6]u8 = undefined;
- leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
- buf[5] = local;
- try self.code.insertSlice(local_offset, &buf);
- local_offset += 6;
- }
+ pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result {
+ switch (typed_value.ty.zigTypeTag()) {
+ .Fn => {
+ try self.genFunctype();
+
+ // Write instructions
+ // TODO: check for and handle death of instructions
+ const mod_fn = blk: {
+ if (typed_value.val.castTag(.function)) |func| break :blk func.data;
+ if (typed_value.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
+ unreachable;
+ };
+
+ // Reserve space to write the size after generating the code as well as space for locals count
+ try self.code.resize(10);
+
+ try self.genBody(mod_fn.body);
+
+ // finally, write our local types at the 'offset' position
+ {
+ leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
+
+ // offset into 'code' section where we will put our locals types
+ var local_offset: usize = 10;
+
+ // emit the actual locals amount
+ for (self.locals.items) |local| {
+ var buf: [6]u8 = undefined;
+ leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
+ buf[5] = local;
+ try self.code.insertSlice(local_offset, &buf);
+ local_offset += 6;
+ }
+ }
+
+ const writer = self.code.writer();
+ try writer.writeByte(wasm.opcode(.end));
+
+ // Fill in the size of the generated code to the reserved space at the
+ // beginning of the buffer.
+ const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5;
+ leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
+
+ // codegen data has been appended to `code`
+ return Result.appended;
+ },
+ .Array => {
+ if (typed_value.val.castTag(.bytes)) |payload| {
+ if (typed_value.ty.sentinel()) |sentinel| {
+ try self.code.appendSlice(payload.data);
+
+ switch (try self.gen(.{
+ .ty = typed_value.ty.elemType(),
+ .val = sentinel,
+ })) {
+ .appended => return Result.appended,
+ .externally_managed => |data| {
+ try self.code.appendSlice(data);
+ return Result.appended;
+ },
+ }
+ }
+ return Result{ .externally_managed = payload.data };
+ } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
+ },
+ .Int => {
+ const info = typed_value.ty.intInfo(self.target);
+ if (info.bits == 8 and info.signedness == .unsigned) {
+ const int_byte = typed_value.val.toUnsignedInt();
+ try self.code.append(@intCast(u8, int_byte));
+ return Result.appended;
+ }
+ return self.fail(.{ .node_offset = 0 }, "TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
+ },
+ else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}),
}
-
- const writer = self.code.writer();
- try writer.writeByte(wasm.opcode(.end));
-
- // Fill in the size of the generated code to the reserved space at the
- // beginning of the buffer.
- const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
- leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
}
fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
return switch (inst.tag) {
- .add => self.genAdd(inst.castTag(.add).?),
+ .add => self.genBinOp(inst.castTag(.add).?, .add),
.alloc => self.genAlloc(inst.castTag(.alloc).?),
.arg => self.genArg(inst.castTag(.arg).?),
.block => self.genBlock(inst.castTag(.block).?),
.breakpoint => self.genBreakpoint(inst.castTag(.breakpoint).?),
.br => self.genBr(inst.castTag(.br).?),
.call => self.genCall(inst.castTag(.call).?),
+ .bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"),
+ .bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"),
+ .bool_or => self.genBinOp(inst.castTag(.bool_or).?, .@"or"),
+ .bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"),
.cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq),
.cmp_gte => self.genCmp(inst.castTag(.cmp_gte).?, .gte),
.cmp_gt => self.genCmp(inst.castTag(.cmp_gt).?, .gt),
@@ -221,10 +725,14 @@ pub const Context = struct {
.dbg_stmt => WValue.none,
.load => self.genLoad(inst.castTag(.load).?),
.loop => self.genLoop(inst.castTag(.loop).?),
+ .mul => self.genBinOp(inst.castTag(.mul).?, .mul),
+ .div => self.genBinOp(inst.castTag(.div).?, .div),
+ .xor => self.genBinOp(inst.castTag(.xor).?, .xor),
.not => self.genNot(inst.castTag(.not).?),
.ret => self.genRet(inst.castTag(.ret).?),
.retvoid => WValue.none,
.store => self.genStore(inst.castTag(.store).?),
+ .sub => self.genBinOp(inst.castTag(.sub).?, .sub),
.unreach => self.genUnreachable(inst.castTag(.unreach).?),
else => self.fail(inst.src, "TODO: Implement wasm inst: {s}", .{inst.tag}),
};
@@ -266,7 +774,7 @@ pub const Context = struct {
// The function index immediate argument will be filled in using this data
// in link.Wasm.flush().
- try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{
+ try self.decl.fn_link.wasm.idx_refs.append(self.gpa, .{
.offset = @intCast(u32, self.code.items.len),
.decl = target,
});
@@ -305,56 +813,76 @@ pub const Context = struct {
return WValue{ .local = self.local_index };
}
- fn genAdd(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
+ fn genBinOp(self: *Context, inst: *Inst.BinOp, op: Op) InnerError!WValue {
const lhs = self.resolveInst(inst.lhs);
const rhs = self.resolveInst(inst.rhs);
try self.emitWValue(lhs);
try self.emitWValue(rhs);
- const opcode: wasm.Opcode = switch (inst.base.ty.tag()) {
- .u32, .i32 => .i32_add,
- .u64, .i64 => .i64_add,
- .f32 => .f32_add,
- .f64 => .f64_add,
- else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}),
- };
-
+ const opcode: wasm.Opcode = buildOpcode(.{
+ .op = op,
+ .valtype1 = try self.typeToValtype(inst.base.src, inst.base.ty),
+ .signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned,
+ });
try self.code.append(wasm.opcode(opcode));
return .none;
}
fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void {
const writer = self.code.writer();
- switch (inst.base.ty.tag()) {
- .u32 => {
- try writer.writeByte(wasm.opcode(.i32_const));
- try leb.writeILEB128(writer, inst.val.toUnsignedInt());
+ switch (inst.base.ty.zigTypeTag()) {
+ .Int => {
+ // write opcode
+ const opcode: wasm.Opcode = buildOpcode(.{
+ .op = .@"const",
+ .valtype1 = try self.typeToValtype(inst.base.src, inst.base.ty),
+ });
+ try writer.writeByte(wasm.opcode(opcode));
+ // write constant
+ switch (inst.base.ty.intInfo(self.target).signedness) {
+ .signed => try leb.writeILEB128(writer, inst.val.toSignedInt()),
+ .unsigned => try leb.writeILEB128(writer, inst.val.toUnsignedInt()),
+ }
},
- .i32, .bool => {
+ .Bool => {
+ // write opcode
try writer.writeByte(wasm.opcode(.i32_const));
+ // write constant
try leb.writeILEB128(writer, inst.val.toSignedInt());
},
- .u64 => {
- try writer.writeByte(wasm.opcode(.i64_const));
- try leb.writeILEB128(writer, inst.val.toUnsignedInt());
- },
- .i64 => {
- try writer.writeByte(wasm.opcode(.i64_const));
- try leb.writeILEB128(writer, inst.val.toSignedInt());
+ .Float => {
+ // write opcode
+ const opcode: wasm.Opcode = buildOpcode(.{
+ .op = .@"const",
+ .valtype1 = try self.typeToValtype(inst.base.src, inst.base.ty),
+ });
+ try writer.writeByte(wasm.opcode(opcode));
+ // write constant
+ switch (inst.base.ty.floatBits(self.target)) {
+ 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, inst.val.toFloat(f32))),
+ 64 => try writer.writeIntLittle(u64, @bitCast(u64, inst.val.toFloat(f64))),
+ else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),
+ }
},
- .f32 => {
- try writer.writeByte(wasm.opcode(.f32_const));
- // TODO: enforce LE byte order
- try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
+ .Pointer => {
+ if (inst.val.castTag(.decl_ref)) |payload| {
+ const decl = payload.data;
+
+ // offset into the offset table within the 'data' section
+ const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
+ try writer.writeByte(wasm.opcode(.i32_const));
+ try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width);
+
+ // memory instruction followed by their memarg immediate
+ // memarg ::== x:u32, y:u32 => {align x, offset y}
+ try writer.writeByte(wasm.opcode(.i32_load));
+ try leb.writeULEB128(writer, @as(u32, 0));
+ try leb.writeULEB128(writer, @as(u32, 0));
+ } else return self.fail(inst.base.src, "Wasm TODO: emitConstant for other const pointer tag {s}", .{inst.val.tag()});
},
- .f64 => {
- try writer.writeByte(wasm.opcode(.f64_const));
- // TODO: enforce LE byte order
- try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
- },
- .void => {},
- else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}),
+ .Void => {},
+ else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),
}
}
@@ -455,62 +983,18 @@ pub const Context = struct {
try self.emitWValue(lhs);
try self.emitWValue(rhs);
- const opcode_maybe: ?wasm.Opcode = switch (op) {
- .lt => @as(?wasm.Opcode, switch (ty) {
- .i32 => .i32_lt_s,
- .u32 => .i32_lt_u,
- .i64 => .i64_lt_s,
- .u64 => .i64_lt_u,
- .f32 => .f32_lt,
- .f64 => .f64_lt,
- else => null,
- }),
- .lte => @as(?wasm.Opcode, switch (ty) {
- .i32 => .i32_le_s,
- .u32 => .i32_le_u,
- .i64 => .i64_le_s,
- .u64 => .i64_le_u,
- .f32 => .f32_le,
- .f64 => .f64_le,
- else => null,
- }),
- .eq => @as(?wasm.Opcode, switch (ty) {
- .i32, .u32 => .i32_eq,
- .i64, .u64 => .i64_eq,
- .f32 => .f32_eq,
- .f64 => .f64_eq,
- else => null,
- }),
- .gte => @as(?wasm.Opcode, switch (ty) {
- .i32 => .i32_ge_s,
- .u32 => .i32_ge_u,
- .i64 => .i64_ge_s,
- .u64 => .i64_ge_u,
- .f32 => .f32_ge,
- .f64 => .f64_ge,
- else => null,
- }),
- .gt => @as(?wasm.Opcode, switch (ty) {
- .i32 => .i32_gt_s,
- .u32 => .i32_gt_u,
- .i64 => .i64_gt_s,
- .u64 => .i64_gt_u,
- .f32 => .f32_gt,
- .f64 => .f64_gt,
- else => null,
- }),
- .neq => @as(?wasm.Opcode, switch (ty) {
- .i32, .u32 => .i32_ne,
- .i64, .u64 => .i64_ne,
- .f32 => .f32_ne,
- .f64 => .f64_ne,
- else => null,
- }),
- };
-
- const opcode = opcode_maybe orelse
- return self.fail(inst.base.src, "TODO - Wasm genCmp for type '{s}' and operator '{s}'", .{ ty, @tagName(op) });
-
+ const opcode: wasm.Opcode = buildOpcode(.{
+ .valtype1 = try self.typeToValtype(inst.base.src, inst.lhs.ty),
+ .op = switch (op) {
+ .lt => .lt,
+ .lte => .le,
+ .eq => .eq,
+ .neq => .ne,
+ .gte => .ge,
+ .gt => .gt,
+ },
+ .signedness = inst.lhs.ty.intInfo(self.target).signedness,
+ });
try self.code.append(wasm.opcode(opcode));
return WValue{ .code_offset = offset };
}
diff --git a/src/ir.zig b/src/ir.zig
@@ -115,6 +115,7 @@ pub const Inst = struct {
unreach,
mul,
mulwrap,
+ div,
not,
floatcast,
intcast,
@@ -181,6 +182,7 @@ pub const Inst = struct {
.subwrap,
.mul,
.mulwrap,
+ .div,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -752,6 +754,7 @@ const DumpTzir = struct {
.subwrap,
.mul,
.mulwrap,
+ .div,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -891,6 +894,7 @@ const DumpTzir = struct {
.subwrap,
.mul,
.mulwrap,
+ .div,
.cmp_lt,
.cmp_lte,
.cmp_eq,
diff --git a/src/link.zig b/src/link.zig
@@ -138,7 +138,7 @@ pub const File = struct {
coff: Coff.TextBlock,
macho: MachO.TextBlock,
c: C.DeclBlock,
- wasm: void,
+ wasm: Wasm.DeclBlock,
spirv: void,
};
@@ -147,7 +147,7 @@ pub const File = struct {
coff: Coff.SrcFn,
macho: MachO.SrcFn,
c: C.FnBlock,
- wasm: ?Wasm.FnData,
+ wasm: Wasm.FnData,
spirv: SpirV.FnData,
};
@@ -328,7 +328,8 @@ pub const File = struct {
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
.macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
.c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
- .wasm, .spirv => {},
+ .wasm => return @fieldParentPtr(Wasm, "base", base).allocateDeclIndexes(decl),
+ .spirv => {},
}
}
diff --git a/src/link/Elf.zig b/src/link/Elf.zig
@@ -2228,10 +2228,9 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
const node_datas = tree.nodes.items(.data);
const token_starts = tree.tokens.items(.start);
- const file_ast_decls = tree.rootDecls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
- const fn_decl = file_ast_decls[decl.src_index];
+ const fn_decl = decl.src_node;
assert(node_tags[fn_decl] == .fn_decl);
const block = node_datas[fn_decl].rhs;
const lbrace = tree.firstToken(block);
@@ -2755,10 +2754,9 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec
const node_datas = tree.nodes.items(.data);
const token_starts = tree.tokens.items(.start);
- const file_ast_decls = tree.rootDecls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
- const fn_decl = file_ast_decls[decl.src_index];
+ const fn_decl = decl.src_node;
assert(node_tags[fn_decl] == .fn_decl);
const block = node_datas[fn_decl].rhs;
const lbrace = tree.firstToken(block);
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
@@ -1256,7 +1256,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
const inst = code_buffer.items[fixup.offset..][0..4];
var parsed = mem.bytesAsValue(meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.PCRelativeAddress,
+ aarch64.Instruction.pc_relative_address,
), inst);
const this_page = @intCast(i32, this_addr >> 12);
const target_page = @intCast(i32, target_addr >> 12);
@@ -1268,7 +1268,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
const inst = code_buffer.items[fixup.offset + 4 ..][0..4];
var parsed = mem.bytesAsValue(meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.LoadStoreRegister,
+ aarch64.Instruction.load_store_register,
), inst);
const narrowed = @truncate(u12, target_addr);
const offset = try math.divExact(u12, narrowed, 8);
diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig
@@ -909,10 +909,9 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M
const node_datas = tree.nodes.items(.data);
const token_starts = tree.tokens.items(.start);
- const file_ast_decls = tree.rootDecls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
- const fn_decl = file_ast_decls[decl.src_index];
+ const fn_decl = decl.src_node;
assert(node_tags[fn_decl] == .fn_decl);
const block = node_datas[fn_decl].rhs;
const lbrace = tree.firstToken(block);
@@ -959,10 +958,9 @@ pub fn initDeclDebugBuffers(
const node_datas = tree.nodes.items(.data);
const token_starts = tree.tokens.items(.start);
- const file_ast_decls = tree.rootDecls();
// TODO Look into improving the performance here by adding a token-index-to-line
// lookup table. Currently this involves scanning over the source code for newlines.
- const fn_decl = file_ast_decls[decl.src_index];
+ const fn_decl = decl.src_node;
assert(node_tags[fn_decl] == .fn_decl);
const block = node_datas[fn_decl].rhs;
const lbrace = tree.firstToken(block);
diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig
@@ -1668,7 +1668,7 @@ fn doRelocs(self: *Zld) !void {
var parsed = mem.bytesAsValue(
meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.UnconditionalBranchImmediate,
+ aarch64.Instruction.unconditional_branch_immediate,
),
inst,
);
@@ -1688,7 +1688,7 @@ fn doRelocs(self: *Zld) !void {
var parsed = mem.bytesAsValue(
meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.PCRelativeAddress,
+ aarch64.Instruction.pc_relative_address,
),
inst,
);
@@ -1706,7 +1706,7 @@ fn doRelocs(self: *Zld) !void {
var parsed = mem.bytesAsValue(
meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.AddSubtractImmediate,
+ aarch64.Instruction.add_subtract_immediate,
),
inst,
);
@@ -1719,7 +1719,7 @@ fn doRelocs(self: *Zld) !void {
var parsed = mem.bytesAsValue(
meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.LoadStoreRegister,
+ aarch64.Instruction.load_store_register,
),
inst,
);
@@ -1774,7 +1774,7 @@ fn doRelocs(self: *Zld) !void {
const curr = mem.bytesAsValue(
meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.AddSubtractImmediate,
+ aarch64.Instruction.add_subtract_immediate,
),
inst,
);
@@ -1783,7 +1783,7 @@ fn doRelocs(self: *Zld) !void {
const curr = mem.bytesAsValue(
meta.TagPayload(
aarch64.Instruction,
- aarch64.Instruction.LoadStoreRegister,
+ aarch64.Instruction.load_store_register,
),
inst,
);
diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig
@@ -16,21 +16,11 @@ const link = @import("../link.zig");
const trace = @import("../tracy.zig").trace;
const build_options = @import("build_options");
const Cache = @import("../Cache.zig");
+const TypedValue = @import("../TypedValue.zig");
pub const base_tag = link.File.Tag.wasm;
-pub const FnData = struct {
- /// Generated code for the type of the function
- functype: std.ArrayListUnmanaged(u8) = .{},
- /// Generated code for the body of the function
- code: std.ArrayListUnmanaged(u8) = .{},
- /// Locations in the generated code where function indexes must be filled in.
- /// This must be kept ordered by offset.
- idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
-};
-
base: link.File,
-
/// List of all function Decls to be written to the output file. The index of
/// each Decl in this list at the time of writing the binary is used as the
/// function index. In the event where ext_funcs' size is not 0, the index of
@@ -45,6 +35,77 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
/// to support existing code.
/// TODO: Allow setting this through a flag?
host_name: []const u8 = "env",
+/// The last `DeclBlock` that was initialized will be saved here.
+last_block: ?*DeclBlock = null,
+/// Table with offsets, each element represents an offset with the value being
+/// the offset into the 'data' section where the data lives
+offset_table: std.ArrayListUnmanaged(u32) = .{},
+/// List of offset indexes which are free to be used for new decl's.
+/// Each element's value points to an index into the offset_table.
+offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
+/// List of all `Decl` that are currently alive.
+/// This is ment for bookkeeping so we can safely cleanup all codegen memory
+/// when calling `deinit`
+symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},
+
+pub const FnData = struct {
+ /// Generated code for the type of the function
+ functype: std.ArrayListUnmanaged(u8),
+ /// Generated code for the body of the function
+ code: std.ArrayListUnmanaged(u8),
+ /// Locations in the generated code where function indexes must be filled in.
+ /// This must be kept ordered by offset.
+ idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }),
+
+ pub const empty: FnData = .{
+ .functype = .{},
+ .code = .{},
+ .idx_refs = .{},
+ };
+};
+
+pub const DeclBlock = struct {
+ /// Determines whether the `DeclBlock` has been initialized for codegen.
+ init: bool,
+ /// Index into the `symbols` list.
+ symbol_index: u32,
+ /// Index into the offset table
+ offset_index: u32,
+ /// The size of the block and how large part of the data section it occupies.
+ /// Will be 0 when the Decl will not live inside the data section and `data` will be undefined.
+ size: u32,
+ /// Points to the previous and next blocks.
+ /// Can be used to find the total size, and used to calculate the `offset` based on the previous block.
+ prev: ?*DeclBlock,
+ next: ?*DeclBlock,
+ /// Pointer to data that will be written to the 'data' section.
+ /// This data either lives in `FnData.code` or is externally managed.
+ /// For data that does not live inside the 'data' section, this field will be undefined. (size == 0).
+ data: [*]const u8,
+
+ pub const empty: DeclBlock = .{
+ .init = false,
+ .symbol_index = 0,
+ .offset_index = 0,
+ .size = 0,
+ .prev = null,
+ .next = null,
+ .data = undefined,
+ };
+
+ /// Unplugs the `DeclBlock` from the chain
+ fn unplug(self: *DeclBlock) void {
+ if (self.prev) |prev| {
+ prev.next = self.next;
+ }
+
+ if (self.next) |next| {
+ next.prev = self.prev;
+ }
+ self.next = null;
+ self.prev = null;
+ }
+};
pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
assert(options.object_format == .wasm);
@@ -52,7 +113,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
- // TODO: read the file and keep vaild parts instead of truncating
+ // TODO: read the file and keep valid parts instead of truncating
const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
errdefer file.close();
@@ -80,58 +141,76 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {
}
pub fn deinit(self: *Wasm) void {
- for (self.funcs.items) |decl| {
- decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
- decl.fn_link.wasm.?.code.deinit(self.base.allocator);
- decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
- }
- for (self.ext_funcs.items) |decl| {
- decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
- decl.fn_link.wasm.?.code.deinit(self.base.allocator);
- decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
+ for (self.symbols.items) |decl| {
+ decl.fn_link.wasm.functype.deinit(self.base.allocator);
+ decl.fn_link.wasm.code.deinit(self.base.allocator);
+ decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
}
+
self.funcs.deinit(self.base.allocator);
self.ext_funcs.deinit(self.base.allocator);
+ self.offset_table.deinit(self.base.allocator);
+ self.offset_table_free_list.deinit(self.base.allocator);
+ self.symbols.deinit(self.base.allocator);
}
-// Generate code for the Decl, storing it in memory to be later written to
-// the file on flush().
-pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
- const typed_value = decl.typed_value.most_recent.typed_value;
- if (typed_value.ty.zigTypeTag() != .Fn)
- return error.TODOImplementNonFnDeclsForWasm;
+pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
+ if (decl.link.wasm.init) return;
+
+ try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
+ try self.symbols.ensureCapacity(self.base.allocator, self.symbols.items.len + 1);
- if (decl.fn_link.wasm) |*fn_data| {
- fn_data.functype.items.len = 0;
- fn_data.code.items.len = 0;
- fn_data.idx_refs.items.len = 0;
+ const block = &decl.link.wasm;
+ block.init = true;
+
+ block.symbol_index = @intCast(u32, self.symbols.items.len);
+ self.symbols.appendAssumeCapacity(decl);
+
+ if (self.offset_table_free_list.popOrNull()) |index| {
+ block.offset_index = index;
} else {
- decl.fn_link.wasm = .{};
- // dependent on function type, appends it to the correct list
- switch (decl.typed_value.most_recent.typed_value.val.tag()) {
+ block.offset_index = @intCast(u32, self.offset_table.items.len);
+ _ = self.offset_table.addOneAssumeCapacity();
+ }
+
+ self.offset_table.items[block.offset_index] = 0;
+
+ const typed_value = decl.typed_value.most_recent.typed_value;
+ if (typed_value.ty.zigTypeTag() == .Fn) {
+ switch (typed_value.val.tag()) {
+ // dependent on function type, appends it to the correct list
.function => try self.funcs.append(self.base.allocator, decl),
.extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
- else => return error.TODOImplementNonFnDeclsForWasm,
+ else => unreachable,
}
}
- const fn_data = &decl.fn_link.wasm.?;
+}
- var managed_functype = fn_data.functype.toManaged(self.base.allocator);
- var managed_code = fn_data.code.toManaged(self.base.allocator);
+// Generate code for the Decl, storing it in memory to be later written to
+// the file on flush().
+pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
+ std.debug.assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
+
+ const typed_value = decl.typed_value.most_recent.typed_value;
+ const fn_data = &decl.fn_link.wasm;
+ fn_data.functype.items.len = 0;
+ fn_data.code.items.len = 0;
+ fn_data.idx_refs.items.len = 0;
var context = codegen.Context{
.gpa = self.base.allocator,
.values = .{},
- .code = managed_code,
- .func_type_data = managed_functype,
+ .code = fn_data.code.toManaged(self.base.allocator),
+ .func_type_data = fn_data.functype.toManaged(self.base.allocator),
.decl = decl,
.err_msg = undefined,
.locals = .{},
+ .target = self.base.options.target,
};
defer context.deinit();
// generate the 'code' section for the function declaration
- context.gen() catch |err| switch (err) {
+ const result = context.gen(typed_value) catch |err| switch (err) {
error.CodegenFail => {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, context.err_msg);
@@ -140,15 +219,38 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
else => |e| return err,
};
- // as locals are patched afterwards, the offsets of funcidx's are off,
- // here we update them to correct them
- for (decl.fn_link.wasm.?.idx_refs.items) |*func| {
- // For each local, add 6 bytes (count + type)
- func.offset += @intCast(u32, context.locals.items.len * 6);
- }
+ const code: []const u8 = switch (result) {
+ .appended => @as([]const u8, context.code.items),
+ .externally_managed => |payload| payload,
+ };
- fn_data.functype = context.func_type_data.toUnmanaged();
fn_data.code = context.code.toUnmanaged();
+ fn_data.functype = context.func_type_data.toUnmanaged();
+
+ const block = &decl.link.wasm;
+ if (typed_value.ty.zigTypeTag() == .Fn) {
+ // as locals are patched afterwards, the offsets of funcidx's are off,
+ // here we update them to correct them
+ for (fn_data.idx_refs.items) |*func| {
+ // For each local, add 6 bytes (count + type)
+ func.offset += @intCast(u32, context.locals.items.len * 6);
+ }
+ } else {
+ block.size = @intCast(u32, code.len);
+ block.data = code.ptr;
+ }
+
+ // If we're updating an existing decl, unplug it first
+ // to avoid infinite loops due to earlier links
+ block.unplug();
+
+ if (self.last_block) |last| {
+ if (last != block) {
+ last.next = block;
+ block.prev = last;
+ }
+ }
+ self.last_block = block;
}
pub fn updateDeclExports(
@@ -159,18 +261,34 @@ pub fn updateDeclExports(
) !void {}
pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
- // TODO: remove this assert when non-function Decls are implemented
- assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
- const func_idx = self.getFuncidx(decl).?;
- switch (decl.typed_value.most_recent.typed_value.val.tag()) {
- .function => _ = self.funcs.swapRemove(func_idx),
- .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
- else => unreachable,
+ if (self.getFuncidx(decl)) |func_idx| {
+ switch (decl.typed_value.most_recent.typed_value.val.tag()) {
+ .function => _ = self.funcs.swapRemove(func_idx),
+ .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
+ else => unreachable,
+ }
+ }
+ const block = &decl.link.wasm;
+
+ if (self.last_block == block) {
+ self.last_block = block.prev;
}
- decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
- decl.fn_link.wasm.?.code.deinit(self.base.allocator);
- decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
- decl.fn_link.wasm = null;
+
+ block.unplug();
+
+ self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};
+ _ = self.symbols.swapRemove(block.symbol_index);
+
+ // update symbol_index as we swap removed the last symbol into the removed's position
+ if (block.symbol_index < self.symbols.items.len)
+ self.symbols.items[block.symbol_index].link.wasm.symbol_index = block.symbol_index;
+
+ block.init = false;
+
+ decl.fn_link.wasm.functype.deinit(self.base.allocator);
+ decl.fn_link.wasm.code.deinit(self.base.allocator);
+ decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
+ decl.fn_link.wasm = undefined;
}
pub fn flush(self: *Wasm, comp: *Compilation) !void {
@@ -187,6 +305,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
const file = self.base.file.?;
const header_size = 5 + 1;
+ // ptr_width in bytes
+ const ptr_width = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
+ // The size of the offset table in bytes
+ // The table contains all decl's with its corresponding offset into
+ // the 'data' section
+ const offset_table_size = @intCast(u32, self.offset_table.items.len * ptr_width);
+
+ // The size of the data, this together with `offset_table_size` amounts to the
+ // total size of the 'data' section
+ var first_decl: ?*DeclBlock = null;
+ const data_size: u32 = if (self.last_block) |last| blk: {
+ var size = last.size;
+ var cur = last;
+ while (cur.prev) |prev| : (cur = prev) {
+ size += prev.size;
+ }
+ first_decl = cur;
+ break :blk size;
+ } else 0;
// No need to rewrite the magic/version header
try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
@@ -198,8 +335,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
// extern functions are defined in the wasm binary first through the `import`
// section, so define their func types first
- for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items);
- for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items);
+ for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);
+ for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);
try writeVecSectionHeader(
file,
@@ -256,6 +393,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
);
}
+ // Memory section
+ if (data_size != 0) {
+ const header_offset = try reserveVecSectionHeader(file);
+ const writer = file.writer();
+
+ try leb.writeULEB128(writer, @as(u32, 0));
+ // Calculate the amount of memory pages are required and write them.
+ // Wasm uses 64kB page sizes. Round up to ensure the data segments fit into the memory
+ try leb.writeULEB128(
+ writer,
+ try std.math.divCeil(
+ u32,
+ offset_table_size + data_size,
+ std.wasm.page_size,
+ ),
+ );
+ try writeVecSectionHeader(
+ file,
+ header_offset,
+ .memory,
+ @intCast(u32, (try file.getPos()) - header_offset - header_size),
+ @as(u32, 1), // wasm currently only supports 1 linear memory segment
+ );
+ }
+
// Export section
if (self.base.options.module) |module| {
const header_offset = try reserveVecSectionHeader(file);
@@ -280,6 +442,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
count += 1;
}
}
+
+ // export memory if size is not 0
+ if (data_size != 0) {
+ try leb.writeULEB128(writer, @intCast(u32, "memory".len));
+ try writer.writeAll("memory");
+ try writer.writeByte(wasm.externalKind(.memory));
+ try leb.writeULEB128(writer, @as(u32, 0)); // only 1 memory 'object' can exist
+ count += 1;
+ }
+
try writeVecSectionHeader(
file,
header_offset,
@@ -294,7 +466,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
const header_offset = try reserveVecSectionHeader(file);
const writer = file.writer();
for (self.funcs.items) |decl| {
- const fn_data = &decl.fn_link.wasm.?;
+ const fn_data = &decl.fn_link.wasm;
// Write the already generated code to the file, inserting
// function indexes where required.
@@ -319,6 +491,51 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
@intCast(u32, self.funcs.items.len),
);
}
+
+ // Data section
+ if (data_size != 0) {
+ const header_offset = try reserveVecSectionHeader(file);
+ const writer = file.writer();
+ var len: u32 = 0;
+ // index to memory section (currently, there can only be 1 memory section in wasm)
+ try leb.writeULEB128(writer, @as(u32, 0));
+
+ // offset into data section
+ try writer.writeByte(wasm.opcode(.i32_const));
+ try leb.writeILEB128(writer, @as(i32, 0));
+ try writer.writeByte(wasm.opcode(.end));
+
+ const total_size = offset_table_size + data_size;
+
+ // offset table + data size
+ try leb.writeULEB128(writer, total_size);
+
+ // fill in the offset table and the data segments
+ const file_offset = try file.getPos();
+ var cur = first_decl;
+ var data_offset = offset_table_size;
+ while (cur) |cur_block| : (cur = cur_block.next) {
+ if (cur_block.size == 0) continue;
+ std.debug.assert(cur_block.init);
+
+ const offset = (cur_block.offset_index) * ptr_width;
+ var buf: [4]u8 = undefined;
+ std.mem.writeIntLittle(u32, &buf, data_offset);
+
+ try file.pwriteAll(&buf, file_offset + offset);
+ try file.pwriteAll(cur_block.data[0..cur_block.size], file_offset + data_offset);
+ data_offset += cur_block.size;
+ }
+
+ try file.seekTo(file_offset + data_offset);
+ try writeVecSectionHeader(
+ file,
+ header_offset,
+ .data,
+ @intCast(u32, (file_offset + data_offset) - header_offset - header_size),
+ @intCast(u32, 1), // only 1 data section
+ );
+ }
}
fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
diff --git a/src/main.zig b/src/main.zig
@@ -505,7 +505,6 @@ fn buildOutputType(
var emit_bin: EmitBin = .yes_default_path;
var emit_asm: Emit = .no;
var emit_llvm_ir: Emit = .no;
- var emit_zir: Emit = .no;
var emit_docs: Emit = .no;
var emit_analysis: Emit = .no;
var target_arch_os_abi: []const u8 = "native";
@@ -599,15 +598,15 @@ fn buildOutputType(
var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
defer test_exec_args.deinit();
- const pkg_tree_root = try gpa.create(Package);
// This package only exists to clean up the code parsing --pkg-begin and
// --pkg-end flags. Use dummy values that are safe for the destroy call.
- pkg_tree_root.* = .{
+ var pkg_tree_root: Package = .{
.root_src_directory = .{ .path = null, .handle = fs.cwd() },
.root_src_path = &[0]u8{},
+ .namespace_hash = Package.root_namespace_hash,
};
- defer pkg_tree_root.destroy(gpa);
- var cur_pkg: *Package = pkg_tree_root;
+ defer freePkgTree(gpa, &pkg_tree_root, false);
+ var cur_pkg: *Package = &pkg_tree_root;
switch (arg_mode) {
.build, .translate_c, .zig_test, .run => {
@@ -658,8 +657,7 @@ fn buildOutputType(
) catch |err| {
fatal("Failed to add package at path {s}: {s}", .{ pkg_path, @errorName(err) });
};
- new_cur_pkg.parent = cur_pkg;
- try cur_pkg.add(gpa, pkg_name, new_cur_pkg);
+ try cur_pkg.addAndAdopt(gpa, pkg_name, new_cur_pkg);
cur_pkg = new_cur_pkg;
} else if (mem.eql(u8, arg, "--pkg-end")) {
cur_pkg = cur_pkg.parent orelse
@@ -924,12 +922,6 @@ fn buildOutputType(
emit_bin = .{ .yes = arg["-femit-bin=".len..] };
} else if (mem.eql(u8, arg, "-fno-emit-bin")) {
emit_bin = .no;
- } else if (mem.eql(u8, arg, "-femit-zir")) {
- emit_zir = .yes_default_path;
- } else if (mem.startsWith(u8, arg, "-femit-zir=")) {
- emit_zir = .{ .yes = arg["-femit-zir=".len..] };
- } else if (mem.eql(u8, arg, "-fno-emit-zir")) {
- emit_zir = .no;
} else if (mem.eql(u8, arg, "-femit-h")) {
emit_h = .yes_default_path;
} else if (mem.startsWith(u8, arg, "-femit-h=")) {
@@ -1026,7 +1018,7 @@ fn buildOutputType(
.extra_flags = try arena.dupe([]const u8, extra_cflags.items),
});
},
- .zig, .zir => {
+ .zig => {
if (root_src_file) |other| {
fatal("found another zig file '{s}' after root source file '{s}'", .{ arg, other });
} else {
@@ -1087,7 +1079,7 @@ fn buildOutputType(
.unknown, .shared_library, .object, .static_library => {
try link_objects.append(it.only_arg);
},
- .zig, .zir => {
+ .zig => {
if (root_src_file) |other| {
fatal("found another zig file '{s}' after root source file '{s}'", .{ it.only_arg, other });
} else {
@@ -1725,13 +1717,6 @@ fn buildOutputType(
var emit_docs_resolved = try emit_docs.resolve("docs");
defer emit_docs_resolved.deinit();
- switch (emit_zir) {
- .no => {},
- .yes_default_path, .yes => {
- fatal("The -femit-zir implementation has been intentionally deleted so that it can be rewritten as a proper backend.", .{});
- },
- }
-
const root_pkg: ?*Package = if (root_src_file) |src_path| blk: {
if (main_pkg_path) |p| {
const rel_src_path = try fs.path.relative(gpa, p, src_path);
@@ -1747,6 +1732,7 @@ fn buildOutputType(
if (root_pkg) |pkg| {
pkg.table = pkg_tree_root.table;
pkg_tree_root.table = .{};
+ pkg.namespace_hash = pkg_tree_root.namespace_hash;
}
const self_exe_path = try fs.selfExePathAlloc(arena);
@@ -1815,6 +1801,11 @@ fn buildOutputType(
@import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv);
}
+ const clang_passthrough_mode = switch (arg_mode) {
+ .cc, .cpp, .translate_c => true,
+ else => false,
+ };
+
gimmeMoreOfThoseSweetSweetFileDescriptors();
const comp = Compilation.create(gpa, .{
@@ -1886,7 +1877,7 @@ fn buildOutputType(
.function_sections = function_sections,
.self_exe_path = self_exe_path,
.thread_pool = &thread_pool,
- .clang_passthrough_mode = arg_mode != .build,
+ .clang_passthrough_mode = clang_passthrough_mode,
.clang_preprocessor_mode = clang_preprocessor_mode,
.version = optional_version,
.libc_installation = if (libc_installation) |*lci| lci else null,
@@ -2101,8 +2092,12 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi
defer errors.deinit(comp.gpa);
if (errors.list.len != 0) {
+ const ttyconf: std.debug.TTY.Config = switch (comp.color) {
+ .auto, .on => std.debug.detectTTYConfig(),
+ .off => .no_color,
+ };
for (errors.list) |full_err_msg| {
- full_err_msg.renderToStdErr();
+ full_err_msg.renderToStdErr(ttyconf);
}
const log_text = comp.getCompileLogOutput();
if (log_text.len != 0) {
@@ -2146,6 +2141,18 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi
}
}
+fn freePkgTree(gpa: *Allocator, pkg: *Package, free_parent: bool) void {
+ {
+ var it = pkg.table.iterator();
+ while (it.next()) |kv| {
+ freePkgTree(gpa, kv.value, true);
+ }
+ }
+ if (free_parent) {
+ pkg.destroy(gpa);
+ }
+}
+
fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !void {
if (!build_options.have_llvm)
fatal("cannot translate-c: compiler built without LLVM extensions", .{});
@@ -2500,6 +2507,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
.handle = try zig_lib_directory.handle.openDir(std_special, .{}),
},
.root_src_path = "build_runner.zig",
+ .namespace_hash = Package.root_namespace_hash,
};
defer root_pkg.root_src_directory.handle.close();
@@ -2545,8 +2553,9 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
var build_pkg: Package = .{
.root_src_directory = build_directory,
.root_src_path = build_zig_basename,
+ .namespace_hash = undefined,
};
- try root_pkg.table.put(arena, "@build", &build_pkg);
+ try root_pkg.addAndAdopt(arena, "@build", &build_pkg);
var global_cache_directory: Compilation.Directory = l: {
const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);
diff --git a/src/register_manager.zig b/src/register_manager.zig
@@ -35,6 +35,10 @@ pub fn RegisterManager(
self.registers.deinit(allocator);
}
+ fn isTracked(reg: Register) bool {
+ return std.mem.indexOfScalar(Register, callee_preserved_regs, reg) != null;
+ }
+
fn markRegUsed(self: *Self, reg: Register) void {
if (FreeRegInt == u0) return;
const index = reg.allocIndex() orelse return;
@@ -51,6 +55,13 @@ pub fn RegisterManager(
self.free_registers |= @as(FreeRegInt, 1) << shift;
}
+ pub fn isRegFree(self: Self, reg: Register) bool {
+ if (FreeRegInt == u0) return true;
+ const index = reg.allocIndex() orelse return true;
+ const shift = @intCast(ShiftInt, index);
+ return self.free_registers & @as(FreeRegInt, 1) << shift != 0;
+ }
+
/// Returns whether this register was allocated in the course
/// of this function
pub fn isRegAllocated(self: Self, reg: Register) bool {
@@ -117,17 +128,61 @@ pub fn RegisterManager(
const regs_entry = self.registers.remove(reg).?;
const spilled_inst = regs_entry.value;
try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+ self.markRegFree(reg);
break :b reg;
};
}
- pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) !void {
- try self.registers.putNoClobber(self.getFunction().gpa, reg, inst);
+ /// Allocates the specified register with the specified
+ /// instruction. Spills the register if it is currently
+ /// allocated.
+ /// Before calling, must ensureCapacity + 1 on self.registers.
+ pub fn getReg(self: *Self, reg: Register, inst: *ir.Inst) !void {
+ if (!isTracked(reg)) return;
+
+ if (!self.isRegFree(reg)) {
+ // Move the instruction that was previously there to a
+ // stack allocation.
+ const regs_entry = self.registers.getEntry(reg).?;
+ const spilled_inst = regs_entry.value;
+ regs_entry.value = inst;
+ try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+ } else {
+ self.getRegAssumeFree(reg, inst);
+ }
+ }
+
+ /// Spills the register if it is currently allocated.
+ /// Does not track the register.
+ pub fn getRegWithoutTracking(self: *Self, reg: Register) !void {
+ if (!isTracked(reg)) return;
+
+ if (!self.isRegFree(reg)) {
+ // Move the instruction that was previously there to a
+ // stack allocation.
+ const regs_entry = self.registers.remove(reg).?;
+ const spilled_inst = regs_entry.value;
+ try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
+ self.markRegFree(reg);
+ }
+ }
+
+ /// Allocates the specified register with the specified
+ /// instruction. Assumes that the register is free and no
+ /// spilling is necessary.
+ /// Before calling, must ensureCapacity + 1 on self.registers.
+ pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) void {
+ if (!isTracked(reg)) return;
+
+ self.registers.putAssumeCapacityNoClobber(reg, inst);
self.markRegUsed(reg);
}
+ /// Marks the specified register as free
pub fn freeReg(self: *Self, reg: Register) void {
+ if (!isTracked(reg)) return;
+
_ = self.registers.remove(reg);
self.markRegFree(reg);
}
@@ -226,3 +281,35 @@ test "allocReg: spilling" {
std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));
std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
}
+
+test "getReg" {
+ const allocator = std.testing.allocator;
+
+ var function = MockFunction{
+ .allocator = allocator,
+ };
+ defer function.deinit();
+
+ var mock_instruction = ir.Inst{
+ .tag = .breakpoint,
+ .ty = Type.initTag(.void),
+ .src = .unneeded,
+ };
+
+ std.testing.expect(!function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(!function.register_manager.isRegAllocated(.r3));
+
+ try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2);
+ try function.register_manager.getReg(.r3, &mock_instruction);
+
+ std.testing.expect(!function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(function.register_manager.isRegAllocated(.r3));
+
+ // Spill r3
+ try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2);
+ try function.register_manager.getReg(.r3, &mock_instruction);
+
+ std.testing.expect(!function.register_manager.isRegAllocated(.r2));
+ std.testing.expect(function.register_manager.isRegAllocated(.r3));
+ std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r3}, function.spilled.items);
+}
diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp
@@ -9110,6 +9110,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
buf_appendf(contents, "pub const position_independent_executable = %s;\n", bool_to_str(g->have_pie));
buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols));
buf_appendf(contents, "pub const code_model = CodeModel.default;\n");
+ buf_appendf(contents, "pub const zig_is_stage2 = false;\n");
{
TargetSubsystem detected_subsystem = detect_subsystem(g);
diff --git a/src/test.zig b/src/test.zig
@@ -122,11 +122,6 @@ pub const TestContext = struct {
path: []const u8,
};
- pub const Extension = enum {
- Zig,
- ZIR,
- };
-
/// A `Case` consists of a list of `Update`. The same `Compilation` is used for each
/// update, so each update's source is treated as a single file being
/// updated by the test harness and incrementally compiled.
@@ -141,7 +136,6 @@ pub const TestContext = struct {
/// to Executable.
output_mode: std.builtin.OutputMode,
updates: std.ArrayList(Update),
- extension: Extension,
object_format: ?std.builtin.ObjectFormat = null,
emit_h: bool = false,
llvm_backend: bool = false,
@@ -238,14 +232,12 @@ pub const TestContext = struct {
ctx: *TestContext,
name: []const u8,
target: CrossTarget,
- extension: Extension,
) *Case {
ctx.cases.append(Case{
.name = name,
.target = target,
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Exe,
- .extension = extension,
.files = std.ArrayList(File).init(ctx.cases.allocator),
}) catch @panic("out of memory");
return &ctx.cases.items[ctx.cases.items.len - 1];
@@ -253,7 +245,7 @@ pub const TestContext = struct {
/// Adds a test case for Zig input, producing an executable
pub fn exe(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
- return ctx.addExe(name, target, .Zig);
+ return ctx.addExe(name, target);
}
/// Adds a test case for ZIR input, producing an executable
@@ -269,7 +261,6 @@ pub const TestContext = struct {
.target = target,
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Exe,
- .extension = .Zig,
.object_format = .c,
.files = std.ArrayList(File).init(ctx.cases.allocator),
}) catch @panic("out of memory");
@@ -284,7 +275,6 @@ pub const TestContext = struct {
.target = target,
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Exe,
- .extension = .Zig,
.files = std.ArrayList(File).init(ctx.cases.allocator),
.llvm_backend = true,
}) catch @panic("out of memory");
@@ -295,14 +285,12 @@ pub const TestContext = struct {
ctx: *TestContext,
name: []const u8,
target: CrossTarget,
- extension: Extension,
) *Case {
ctx.cases.append(Case{
.name = name,
.target = target,
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Obj,
- .extension = extension,
.files = std.ArrayList(File).init(ctx.cases.allocator),
}) catch @panic("out of memory");
return &ctx.cases.items[ctx.cases.items.len - 1];
@@ -310,7 +298,7 @@ pub const TestContext = struct {
/// Adds a test case for Zig input, producing an object file.
pub fn obj(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
- return ctx.addObj(name, target, .Zig);
+ return ctx.addObj(name, target);
}
/// Adds a test case for ZIR input, producing an object file.
@@ -319,13 +307,12 @@ pub const TestContext = struct {
}
/// Adds a test case for Zig or ZIR input, producing C code.
- pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget, ext: Extension) *Case {
+ pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
ctx.cases.append(Case{
.name = name,
.target = target,
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Obj,
- .extension = ext,
.object_format = .c,
.files = std.ArrayList(File).init(ctx.cases.allocator),
}) catch @panic("out of memory");
@@ -333,21 +320,20 @@ pub const TestContext = struct {
}
pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
- ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out);
+ ctx.addC(name, target).addCompareObjectFile(src, zig_h ++ out);
}
pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
- ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out);
+ ctx.addC(name, target).addHeader(src, zig_h ++ out);
}
pub fn addCompareOutput(
ctx: *TestContext,
name: []const u8,
- extension: Extension,
src: [:0]const u8,
expected_stdout: []const u8,
) void {
- ctx.addExe(name, .{}, extension).addCompareOutput(src, expected_stdout);
+ ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout);
}
/// Adds a test case that compiles the Zig source given in `src`, executes
@@ -358,7 +344,7 @@ pub const TestContext = struct {
src: [:0]const u8,
expected_stdout: []const u8,
) void {
- return ctx.addCompareOutput(name, .Zig, src, expected_stdout);
+ return ctx.addCompareOutput(name, src, expected_stdout);
}
/// Adds a test case that compiles the ZIR source given in `src`, executes
@@ -376,11 +362,10 @@ pub const TestContext = struct {
ctx: *TestContext,
name: []const u8,
target: CrossTarget,
- extension: Extension,
src: [:0]const u8,
result: [:0]const u8,
) void {
- ctx.addObj(name, target, extension).addTransform(src, result);
+ ctx.addObj(name, target).addTransform(src, result);
}
/// Adds a test case that compiles the Zig given in `src` to ZIR and tests
@@ -392,7 +377,7 @@ pub const TestContext = struct {
src: [:0]const u8,
result: [:0]const u8,
) void {
- ctx.addTransform(name, target, .Zig, src, result);
+ ctx.addTransform(name, target, src, result);
}
/// Adds a test case that cleans up the ZIR source given in `src`, and
@@ -411,11 +396,10 @@ pub const TestContext = struct {
ctx: *TestContext,
name: []const u8,
target: CrossTarget,
- extension: Extension,
src: [:0]const u8,
expected_errors: []const []const u8,
) void {
- ctx.addObj(name, target, extension).addError(src, expected_errors);
+ ctx.addObj(name, target).addError(src, expected_errors);
}
/// Adds a test case that ensures that the Zig given in `src` fails to
@@ -428,7 +412,7 @@ pub const TestContext = struct {
src: [:0]const u8,
expected_errors: []const []const u8,
) void {
- ctx.addError(name, target, .Zig, src, expected_errors);
+ ctx.addError(name, target, src, expected_errors);
}
/// Adds a test case that ensures that the ZIR given in `src` fails to
@@ -448,10 +432,9 @@ pub const TestContext = struct {
ctx: *TestContext,
name: []const u8,
target: CrossTarget,
- extension: Extension,
src: [:0]const u8,
) void {
- ctx.addObj(name, target, extension).compiles(src);
+ ctx.addObj(name, target).compiles(src);
}
/// Adds a test case that asserts that the Zig given in `src` compiles
@@ -462,7 +445,7 @@ pub const TestContext = struct {
target: CrossTarget,
src: [:0]const u8,
) void {
- ctx.addCompiles(name, target, .Zig, src);
+ ctx.addCompiles(name, target, src);
}
/// Adds a test case that asserts that the ZIR given in `src` compiles
@@ -489,7 +472,7 @@ pub const TestContext = struct {
expected_errors: []const []const u8,
fixed_src: [:0]const u8,
) void {
- var case = ctx.addObj(name, target, .Zig);
+ var case = ctx.addObj(name, target);
case.addError(src, expected_errors);
case.compiles(fixed_src);
}
@@ -614,15 +597,14 @@ pub const TestContext = struct {
.path = try std.fs.path.join(arena, &[_][]const u8{ tmp_dir_path, "zig-cache" }),
};
- const tmp_src_path = switch (case.extension) {
- .Zig => "test_case.zig",
- .ZIR => "test_case.zir",
- };
+ const tmp_src_path = "test_case.zig";
var root_pkg: Package = .{
.root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir },
.root_src_path = tmp_src_path,
+ .namespace_hash = Package.root_namespace_hash,
};
+ defer root_pkg.table.deinit(allocator);
const bin_name = try std.zig.binNameAlloc(arena, .{
.root_name = "test_case",
@@ -639,13 +621,10 @@ pub const TestContext = struct {
.directory = emit_directory,
.basename = bin_name,
};
- const emit_h: ?Compilation.EmitLoc = if (case.emit_h)
- .{
- .directory = emit_directory,
- .basename = "test_case.h",
- }
- else
- null;
+ const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{
+ .directory = emit_directory,
+ .basename = "test_case.h",
+ } else null;
const comp = try Compilation.create(allocator, .{
.local_cache_directory = zig_cache_directory,
.global_cache_directory = global_cache_directory,
diff --git a/src/translate_c.zig b/src/translate_c.zig
@@ -1123,6 +1123,16 @@ fn transStmt(
const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, stmt);
return transExpr(c, scope, gen_sel.getResultExpr(), result_used);
},
+ .ConvertVectorExprClass => {
+ const conv_vec = @ptrCast(*const clang.ConvertVectorExpr, stmt);
+ const conv_vec_node = try transConvertVectorExpr(c, scope, stmt.getBeginLoc(), conv_vec);
+ return maybeSuppressResult(c, scope, result_used, conv_vec_node);
+ },
+ .ShuffleVectorExprClass => {
+ const shuffle_vec_expr = @ptrCast(*const clang.ShuffleVectorExpr, stmt);
+ const shuffle_vec_node = try transShuffleVectorExpr(c, scope, shuffle_vec_expr);
+ return maybeSuppressResult(c, scope, result_used, shuffle_vec_node);
+ },
// When adding new cases here, see comment for maybeBlockify()
else => {
return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)});
@@ -1130,6 +1140,128 @@ fn transStmt(
}
}
+/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-convertvector
+fn transConvertVectorExpr(
+ c: *Context,
+ scope: *Scope,
+ source_loc: clang.SourceLocation,
+ expr: *const clang.ConvertVectorExpr,
+) TransError!Node {
+ const base_stmt = @ptrCast(*const clang.Stmt, expr);
+
+ var block_scope = try Scope.Block.init(c, scope, true);
+ defer block_scope.deinit();
+
+ const src_expr = expr.getSrcExpr();
+ const src_type = qualTypeCanon(src_expr.getType());
+ const src_vector_ty = @ptrCast(*const clang.VectorType, src_type);
+ const src_element_qt = src_vector_ty.getElementType();
+ const src_element_type_node = try transQualType(c, &block_scope.base, src_element_qt, base_stmt.getBeginLoc());
+
+ const src_expr_node = try transExpr(c, &block_scope.base, src_expr, .used);
+
+ const dst_qt = expr.getTypeSourceInfo_getType();
+ const dst_type_node = try transQualType(c, &block_scope.base, dst_qt, base_stmt.getBeginLoc());
+ const dst_vector_ty = @ptrCast(*const clang.VectorType, qualTypeCanon(dst_qt));
+ const num_elements = dst_vector_ty.getNumElements();
+ const dst_element_qt = dst_vector_ty.getElementType();
+
+ // workaround for https://github.com/ziglang/zig/issues/8322
+ // we store the casted results into temp variables and use those
+ // to initialize the vector. Eventually we can just directly
+ // construct the init_list from casted source members
+ var i: usize = 0;
+ while (i < num_elements) : (i += 1) {
+ const mangled_name = try block_scope.makeMangledName(c, "tmp");
+ const value = try Tag.array_access.create(c.arena, .{
+ .lhs = src_expr_node,
+ .rhs = try transCreateNodeNumber(c, i, .int),
+ });
+ const tmp_decl_node = try Tag.var_simple.create(c.arena, .{
+ .name = mangled_name,
+ .init = try transCCast(c, &block_scope.base, base_stmt.getBeginLoc(), dst_element_qt, src_element_qt, value),
+ });
+ try block_scope.statements.append(tmp_decl_node);
+ }
+
+ const init_list = try c.arena.alloc(Node, num_elements);
+ for (init_list) |*init, init_index| {
+ const tmp_decl = block_scope.statements.items[init_index];
+ const name = tmp_decl.castTag(.var_simple).?.data.name;
+ init.* = try Tag.identifier.create(c.arena, name);
+ }
+
+ const vec_init = try Tag.array_init.create(c.arena, .{
+ .cond = dst_type_node,
+ .cases = init_list,
+ });
+
+ const break_node = try Tag.break_val.create(c.arena, .{
+ .label = block_scope.label,
+ .val = vec_init,
+ });
+ try block_scope.statements.append(break_node);
+ return block_scope.complete(c);
+}
+
+fn makeShuffleMask(c: *Context, scope: *Scope, expr: *const clang.ShuffleVectorExpr, vector_len: Node) TransError!Node {
+ const num_subexprs = expr.getNumSubExprs();
+ assert(num_subexprs >= 3); // two source vectors + at least 1 index expression
+ const mask_len = num_subexprs - 2;
+
+ const mask_type = try Tag.std_meta_vector.create(c.arena, .{
+ .lhs = try transCreateNodeNumber(c, mask_len, .int),
+ .rhs = try Tag.type.create(c.arena, "i32"),
+ });
+
+ const init_list = try c.arena.alloc(Node, mask_len);
+
+ for (init_list) |*init, i| {
+ const index_expr = try transExprCoercing(c, scope, expr.getExpr(@intCast(c_uint, i + 2)), .used);
+ const converted_index = try Tag.std_meta_shuffle_vector_index.create(c.arena, .{ .lhs = index_expr, .rhs = vector_len });
+ init.* = converted_index;
+ }
+
+ const mask_init = try Tag.array_init.create(c.arena, .{
+ .cond = mask_type,
+ .cases = init_list,
+ });
+ return Tag.@"comptime".create(c.arena, mask_init);
+}
+
+/// @typeInfo(@TypeOf(vec_node)).Vector.<field>
+fn vectorTypeInfo(arena: *mem.Allocator, vec_node: Node, field: []const u8) TransError!Node {
+ const typeof_call = try Tag.typeof.create(arena, vec_node);
+ const typeinfo_call = try Tag.typeinfo.create(arena, typeof_call);
+ const vector_type_info = try Tag.field_access.create(arena, .{ .lhs = typeinfo_call, .field_name = "Vector" });
+ return Tag.field_access.create(arena, .{ .lhs = vector_type_info, .field_name = field });
+}
+
+fn transShuffleVectorExpr(
+ c: *Context,
+ scope: *Scope,
+ expr: *const clang.ShuffleVectorExpr,
+) TransError!Node {
+ const base_expr = @ptrCast(*const clang.Expr, expr);
+ const num_subexprs = expr.getNumSubExprs();
+ if (num_subexprs < 3) return fail(c, error.UnsupportedTranslation, base_expr.getBeginLoc(), "ShuffleVector needs at least 1 index", .{});
+
+ const a = try transExpr(c, scope, expr.getExpr(0), .used);
+ const b = try transExpr(c, scope, expr.getExpr(1), .used);
+
+ // clang requires first two arguments to __builtin_shufflevector to be same type
+ const vector_child_type = try vectorTypeInfo(c.arena, a, "child");
+ const vector_len = try vectorTypeInfo(c.arena, a, "len");
+ const shuffle_mask = try makeShuffleMask(c, scope, expr, vector_len);
+
+ return Tag.shuffle.create(c.arena, .{
+ .element_type = vector_child_type,
+ .a = a,
+ .b = b,
+ .mask_vector = shuffle_mask,
+ });
+}
+
/// Translate a "simple" offsetof expression containing exactly one component,
/// when that component is of kind .Field - e.g. offsetof(mytype, myfield)
fn transSimpleOffsetOfExpr(
@@ -1935,6 +2067,10 @@ fn cIsEnum(qt: clang.QualType) bool {
return qt.getCanonicalType().getTypeClass() == .Enum;
}
+fn cIsVector(qt: clang.QualType) bool {
+ return qt.getCanonicalType().getTypeClass() == .Vector;
+}
+
/// Get the underlying int type of an enum. The C compiler chooses a signed int
/// type that is large enough to hold all of the enum's values. It is not required
/// to be the smallest possible type that can hold all the values.
@@ -1991,6 +2127,11 @@ fn transCCast(
// @bitCast(dest_type, intermediate_value)
return Tag.bit_cast.create(c.arena, .{ .lhs = dst_node, .rhs = src_int_expr });
}
+ if (cIsVector(src_type) or cIsVector(dst_type)) {
+ // C cast where at least 1 operand is a vector requires them to be same size
+ // @bitCast(dest_type, val)
+ return Tag.bit_cast.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
+ }
if (cIsInteger(dst_type) and qualTypeIsPtr(src_type)) {
// @intCast(dest_type, @ptrToInt(val))
const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr);
@@ -2209,6 +2350,63 @@ fn transInitListExprArray(
}
}
+fn transInitListExprVector(
+ c: *Context,
+ scope: *Scope,
+ loc: clang.SourceLocation,
+ expr: *const clang.InitListExpr,
+ ty: *const clang.Type,
+) TransError!Node {
+
+ const qt = getExprQualType(c, @ptrCast(*const clang.Expr, expr));
+ const vector_type = try transQualType(c, scope, qt, loc);
+ const init_count = expr.getNumInits();
+
+ if (init_count == 0) {
+ return Tag.container_init.create(c.arena, .{
+ .lhs = vector_type,
+ .inits = try c.arena.alloc(ast.Payload.ContainerInit.Initializer, 0),
+ });
+ }
+
+ var block_scope = try Scope.Block.init(c, scope, true);
+ defer block_scope.deinit();
+
+ // workaround for https://github.com/ziglang/zig/issues/8322
+ // we store the initializers in temp variables and use those
+ // to initialize the vector. Eventually we can just directly
+ // construct the init_list from casted source members
+ var i: usize = 0;
+ while (i < init_count) : (i += 1) {
+ const mangled_name = try block_scope.makeMangledName(c, "tmp");
+ const init_expr = expr.getInit(@intCast(c_uint, i));
+ const tmp_decl_node = try Tag.var_simple.create(c.arena, .{
+ .name = mangled_name,
+ .init = try transExpr(c, &block_scope.base, init_expr, .used),
+ });
+ try block_scope.statements.append(tmp_decl_node);
+ }
+
+ const init_list = try c.arena.alloc(Node, init_count);
+ for (init_list) |*init, init_index| {
+ const tmp_decl = block_scope.statements.items[init_index];
+ const name = tmp_decl.castTag(.var_simple).?.data.name;
+ init.* = try Tag.identifier.create(c.arena, name);
+ }
+
+ const array_init = try Tag.array_init.create(c.arena, .{
+ .cond = vector_type,
+ .cases = init_list,
+ });
+ const break_node = try Tag.break_val.create(c.arena, .{
+ .label = block_scope.label,
+ .val = array_init,
+ });
+ try block_scope.statements.append(break_node);
+
+ return block_scope.complete(c);
+}
+
fn transInitListExpr(
c: *Context,
scope: *Scope,
@@ -2235,6 +2433,14 @@ fn transInitListExpr(
expr,
qual_type,
));
+ } else if (qual_type.isVectorType()) {
+ return maybeSuppressResult(c, scope, used, try transInitListExprVector(
+ c,
+ scope,
+ source_loc,
+ expr,
+ qual_type,
+ ));
} else {
const type_name = c.str(qual_type.getTypeClassName());
return fail(c, error.UnsupportedType, source_loc, "unsupported initlist type: '{s}'", .{type_name});
@@ -4085,6 +4291,15 @@ fn transType(c: *Context, scope: *Scope, ty: *const clang.Type, source_loc: clan
};
return Tag.typeof.create(c.arena, underlying_expr);
},
+ .Vector => {
+ const vector_ty = @ptrCast(*const clang.VectorType, ty);
+ const num_elements = vector_ty.getNumElements();
+ const element_qt = vector_ty.getElementType();
+ return Tag.std_meta_vector.create(c.arena, .{
+ .lhs = try transCreateNodeNumber(c, num_elements, .int),
+ .rhs = try transQualType(c, scope, element_qt, source_loc),
+ });
+ },
else => {
const type_name = c.str(ty.getTypeClassName());
return fail(c, error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{type_name});
@@ -5211,21 +5426,26 @@ fn parseCPostfixExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
}
},
.LParen => {
- var args = std.ArrayList(Node).init(c.gpa);
- defer args.deinit();
- while (true) {
- const arg = try parseCCondExpr(c, m, scope);
- try args.append(arg);
- switch (m.next().?) {
- .Comma => {},
- .RParen => break,
- else => {
- try m.fail(c, "unable to translate C expr: expected ',' or ')'", .{});
- return error.ParseError;
- },
+ if (m.peek().? == .RParen) {
+ m.i += 1;
+ node = try Tag.call.create(c.arena, .{ .lhs = node, .args = &[0]Node{} });
+ } else {
+ var args = std.ArrayList(Node).init(c.gpa);
+ defer args.deinit();
+ while (true) {
+ const arg = try parseCCondExpr(c, m, scope);
+ try args.append(arg);
+ switch (m.next().?) {
+ .Comma => {},
+ .RParen => break,
+ else => {
+ try m.fail(c, "unable to translate C expr: expected ',' or ')'", .{});
+ return error.ParseError;
+ },
+ }
}
+ node = try Tag.call.create(c.arena, .{ .lhs = node, .args = try c.arena.dupe(Node, args.items) });
}
- node = try Tag.call.create(c.arena, .{ .lhs = node, .args = try c.arena.dupe(Node, args.items) });
},
.LBrace => {
var init_vals = std.ArrayList(Node).init(c.gpa);
diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig
@@ -66,6 +66,7 @@ pub const Node = extern union {
@"enum",
@"struct",
@"union",
+ @"comptime",
array_init,
tuple,
container_init,
@@ -154,6 +155,8 @@ pub const Node = extern union {
div_exact,
/// @byteOffsetOf(lhs, rhs)
byte_offset_of,
+ /// @shuffle(type, a, b, mask)
+ shuffle,
negate,
negate_wrap,
@@ -172,6 +175,7 @@ pub const Node = extern union {
sizeof,
alignof,
typeof,
+ typeinfo,
type,
optional_type,
@@ -182,6 +186,10 @@ pub const Node = extern union {
/// @import("std").meta.sizeof(operand)
std_meta_sizeof,
+ /// @import("std").meta.shuffleVectorIndex(lhs, rhs)
+ std_meta_shuffle_vector_index,
+ /// @import("std").meta.Vector(lhs, rhs)
+ std_meta_vector,
/// @import("std").mem.zeroes(operand)
std_mem_zeroes,
/// @import("std").mem.zeroInit(lhs, rhs)
@@ -233,6 +241,7 @@ pub const Node = extern union {
.std_mem_zeroes,
.@"return",
+ .@"comptime",
.discard,
.std_math_Log2Int,
.negate,
@@ -255,6 +264,7 @@ pub const Node = extern union {
.sizeof,
.alignof,
.typeof,
+ .typeinfo,
=> Payload.UnOp,
.add,
@@ -308,6 +318,8 @@ pub const Node = extern union {
.align_cast,
.array_access,
.std_mem_zeroinit,
+ .std_meta_shuffle_vector_index,
+ .std_meta_vector,
.ptr_cast,
.div_exact,
.byte_offset_of,
@@ -346,6 +358,7 @@ pub const Node = extern union {
.pub_inline_fn => Payload.PubInlineFn,
.field_access => Payload.FieldAccess,
.string_slice => Payload.StringSlice,
+ .shuffle => Payload.Shuffle,
};
}
@@ -678,6 +691,16 @@ pub const Payload = struct {
end: usize,
},
};
+
+ pub const Shuffle = struct {
+ base: Payload,
+ data: struct {
+ element_type: Node,
+ a: Node,
+ b: Node,
+ mask_vector: Node,
+ },
+ };
};
/// Converts the nodes into a Zig ast.
@@ -868,6 +891,16 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
const import_node = try renderStdImport(c, "mem", "zeroInit");
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
},
+ .std_meta_shuffle_vector_index => {
+ const payload = node.castTag(.std_meta_shuffle_vector_index).?.data;
+ const import_node = try renderStdImport(c, "meta", "shuffleVectorIndex");
+ return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
+ },
+ .std_meta_vector => {
+ const payload = node.castTag(.std_meta_vector).?.data;
+ const import_node = try renderStdImport(c, "meta", "Vector");
+ return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
+ },
.call => {
const payload = node.castTag(.call).?.data;
const lhs = try renderNode(c, payload.lhs);
@@ -964,6 +997,17 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
},
});
},
+ .@"comptime" => {
+ const payload = node.castTag(.@"comptime").?.data;
+ return c.addNode(.{
+ .tag = .@"comptime",
+ .main_token = try c.addToken(.keyword_comptime, "comptime"),
+ .data = .{
+ .lhs = try renderNode(c, payload),
+ .rhs = undefined,
+ },
+ });
+ },
.type => {
const payload = node.castTag(.type).?.data;
return c.addNode(.{
@@ -1217,6 +1261,15 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
const payload = node.castTag(.sizeof).?.data;
return renderBuiltinCall(c, "@sizeOf", &.{payload});
},
+ .shuffle => {
+ const payload = node.castTag(.shuffle).?.data;
+ return renderBuiltinCall(c, "@shuffle", &.{
+ payload.element_type,
+ payload.a,
+ payload.b,
+ payload.mask_vector,
+ });
+ },
.alignof => {
const payload = node.castTag(.alignof).?.data;
return renderBuiltinCall(c, "@alignOf", &.{payload});
@@ -1225,6 +1278,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
const payload = node.castTag(.typeof).?.data;
return renderBuiltinCall(c, "@TypeOf", &.{payload});
},
+ .typeinfo => {
+ const payload = node.castTag(.typeinfo).?.data;
+ return renderBuiltinCall(c, "@typeInfo", &.{payload});
+ },
.negate => return renderPrefixOp(c, node, .negation, .minus, "-"),
.negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"),
.bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"),
@@ -2085,9 +2142,12 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
.sizeof,
.alignof,
.typeof,
+ .typeinfo,
.std_meta_sizeof,
.std_meta_cast,
.std_meta_promoteIntLiteral,
+ .std_meta_vector,
+ .std_meta_shuffle_vector_index,
.std_mem_zeroinit,
.integer_literal,
.float_literal,
@@ -2118,6 +2178,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
.bool_to_int,
.div_exact,
.byte_offset_of,
+ .shuffle,
=> {
// no grouping needed
return renderNode(c, node);
@@ -2185,6 +2246,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
.discard,
.@"continue",
.@"return",
+ .@"comptime",
.usingnamespace_builtins,
.while_true,
.if_not_break,
@@ -2327,6 +2389,8 @@ fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !Node
_ = try c.addToken(.l_paren, "(");
var arg_1: NodeIndex = 0;
var arg_2: NodeIndex = 0;
+ var arg_3: NodeIndex = 0;
+ var arg_4: NodeIndex = 0;
switch (args.len) {
0 => {},
1 => {
@@ -2337,18 +2401,41 @@ fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !Node
_ = try c.addToken(.comma, ",");
arg_2 = try renderNode(c, args[1]);
},
+ 4 => {
+ arg_1 = try renderNode(c, args[0]);
+ _ = try c.addToken(.comma, ",");
+ arg_2 = try renderNode(c, args[1]);
+ _ = try c.addToken(.comma, ",");
+ arg_3 = try renderNode(c, args[2]);
+ _ = try c.addToken(.comma, ",");
+ arg_4 = try renderNode(c, args[3]);
+ },
else => unreachable, // expand this function as needed.
}
_ = try c.addToken(.r_paren, ")");
- return c.addNode(.{
- .tag = .builtin_call_two,
- .main_token = builtin_tok,
- .data = .{
- .lhs = arg_1,
- .rhs = arg_2,
- },
- });
+ if (args.len <= 2) {
+ return c.addNode(.{
+ .tag = .builtin_call_two,
+ .main_token = builtin_tok,
+ .data = .{
+ .lhs = arg_1,
+ .rhs = arg_2,
+ },
+ });
+ } else {
+ std.debug.assert(args.len == 4);
+
+ const params = try c.listToSpan(&.{ arg_1, arg_2, arg_3, arg_4 });
+ return c.addNode(.{
+ .tag = .builtin_call,
+ .main_token = builtin_tok,
+ .data = .{
+ .lhs = params.start,
+ .rhs = params.end,
+ },
+ });
+ }
}
fn renderVar(c: *Context, node: Node) !NodeIndex {
diff --git a/src/type.zig b/src/type.zig
@@ -93,14 +93,56 @@ pub const Type = extern union {
.anyerror_void_error_union, .error_union => return .ErrorUnion,
- .empty_struct => return .Struct,
- .empty_struct_literal => return .Struct,
- .@"struct" => return .Struct,
+ .empty_struct,
+ .empty_struct_literal,
+ .@"struct",
+ => return .Struct,
+
+ .enum_full,
+ .enum_nonexhaustive,
+ .enum_simple,
+ => return .Enum,
.var_args_param => unreachable, // can be any type
}
}
+ pub fn isSelfComparable(ty: Type, is_equality_cmp: bool) bool {
+ return switch (ty.zigTypeTag()) {
+ .Int,
+ .Float,
+ .ComptimeFloat,
+ .ComptimeInt,
+ .Vector, // TODO some vectors require is_equality_cmp==true
+ => true,
+
+ .Bool,
+ .Type,
+ .Void,
+ .ErrorSet,
+ .Fn,
+ .BoundFn,
+ .Opaque,
+ .AnyFrame,
+ .Enum,
+ .EnumLiteral,
+ => is_equality_cmp,
+
+ .NoReturn,
+ .Array,
+ .Struct,
+ .Undefined,
+ .Null,
+ .ErrorUnion,
+ .Union,
+ .Frame,
+ => false,
+
+ .Pointer => is_equality_cmp or ty.isCPtr(),
+ .Optional => is_equality_cmp and ty.isPtrLikeOptional(),
+ };
+ }
+
pub fn initTag(comptime small_tag: Tag) Type {
comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
return .{ .tag_if_small_enough = @enumToInt(small_tag) };
@@ -614,6 +656,8 @@ pub const Type = extern union {
.error_set_single => return self.copyPayloadShallow(allocator, Payload.Name),
.empty_struct => return self.copyPayloadShallow(allocator, Payload.ContainerScope),
.@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct),
+ .enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple),
+ .enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull),
.@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque),
}
}
@@ -626,13 +670,13 @@ pub const Type = extern union {
}
pub fn format(
- self: Type,
+ start_type: Type,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
- out_stream: anytype,
- ) @TypeOf(out_stream).Error!void {
+ writer: anytype,
+ ) @TypeOf(writer).Error!void {
comptime assert(fmt.len == 0);
- var ty = self;
+ var ty = start_type;
while (true) {
const t = ty.tag();
switch (t) {
@@ -670,132 +714,149 @@ pub const Type = extern union {
.comptime_float,
.noreturn,
.var_args_param,
- => return out_stream.writeAll(@tagName(t)),
-
- .enum_literal => return out_stream.writeAll("@Type(.EnumLiteral)"),
- .@"null" => return out_stream.writeAll("@Type(.Null)"),
- .@"undefined" => return out_stream.writeAll("@Type(.Undefined)"),
-
- .empty_struct, .empty_struct_literal => return out_stream.writeAll("struct {}"),
- .@"struct" => return out_stream.writeAll("(struct)"),
- .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"),
- .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
- .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"),
- .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"),
+ => return writer.writeAll(@tagName(t)),
+
+ .enum_literal => return writer.writeAll("@Type(.EnumLiteral)"),
+ .@"null" => return writer.writeAll("@Type(.Null)"),
+ .@"undefined" => return writer.writeAll("@Type(.Undefined)"),
+
+ .empty_struct, .empty_struct_literal => return writer.writeAll("struct {}"),
+
+ .@"struct" => {
+ const struct_obj = ty.castTag(.@"struct").?.data;
+ return struct_obj.owner_decl.renderFullyQualifiedName(writer);
+ },
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Payload.EnumFull).?.data;
+ return enum_full.owner_decl.renderFullyQualifiedName(writer);
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ return enum_simple.owner_decl.renderFullyQualifiedName(writer);
+ },
+ .@"opaque" => {
+ // TODO use declaration name
+ return writer.writeAll("opaque {}");
+ },
+
+ .anyerror_void_error_union => return writer.writeAll("anyerror!void"),
+ .const_slice_u8 => return writer.writeAll("[]const u8"),
+ .fn_noreturn_no_args => return writer.writeAll("fn() noreturn"),
+ .fn_void_no_args => return writer.writeAll("fn() void"),
+ .fn_naked_noreturn_no_args => return writer.writeAll("fn() callconv(.Naked) noreturn"),
+ .fn_ccc_void_no_args => return writer.writeAll("fn() callconv(.C) void"),
+ .single_const_pointer_to_comptime_int => return writer.writeAll("*const comptime_int"),
.function => {
const payload = ty.castTag(.function).?.data;
- try out_stream.writeAll("fn(");
+ try writer.writeAll("fn(");
for (payload.param_types) |param_type, i| {
- if (i != 0) try out_stream.writeAll(", ");
- try param_type.format("", .{}, out_stream);
+ if (i != 0) try writer.writeAll(", ");
+ try param_type.format("", .{}, writer);
}
if (payload.is_var_args) {
if (payload.param_types.len != 0) {
- try out_stream.writeAll(", ");
+ try writer.writeAll(", ");
}
- try out_stream.writeAll("...");
+ try writer.writeAll("...");
}
- try out_stream.writeAll(") callconv(.");
- try out_stream.writeAll(@tagName(payload.cc));
- try out_stream.writeAll(")");
+ try writer.writeAll(") callconv(.");
+ try writer.writeAll(@tagName(payload.cc));
+ try writer.writeAll(")");
ty = payload.return_type;
continue;
},
.array_u8 => {
const len = ty.castTag(.array_u8).?.data;
- return out_stream.print("[{d}]u8", .{len});
+ return writer.print("[{d}]u8", .{len});
},
.array_u8_sentinel_0 => {
const len = ty.castTag(.array_u8_sentinel_0).?.data;
- return out_stream.print("[{d}:0]u8", .{len});
+ return writer.print("[{d}:0]u8", .{len});
},
.array => {
const payload = ty.castTag(.array).?.data;
- try out_stream.print("[{d}]", .{payload.len});
+ try writer.print("[{d}]", .{payload.len});
ty = payload.elem_type;
continue;
},
.array_sentinel => {
const payload = ty.castTag(.array_sentinel).?.data;
- try out_stream.print("[{d}:{}]", .{ payload.len, payload.sentinel });
+ try writer.print("[{d}:{}]", .{ payload.len, payload.sentinel });
ty = payload.elem_type;
continue;
},
.single_const_pointer => {
const pointee_type = ty.castTag(.single_const_pointer).?.data;
- try out_stream.writeAll("*const ");
+ try writer.writeAll("*const ");
ty = pointee_type;
continue;
},
.single_mut_pointer => {
const pointee_type = ty.castTag(.single_mut_pointer).?.data;
- try out_stream.writeAll("*");
+ try writer.writeAll("*");
ty = pointee_type;
continue;
},
.many_const_pointer => {
const pointee_type = ty.castTag(.many_const_pointer).?.data;
- try out_stream.writeAll("[*]const ");
+ try writer.writeAll("[*]const ");
ty = pointee_type;
continue;
},
.many_mut_pointer => {
const pointee_type = ty.castTag(.many_mut_pointer).?.data;
- try out_stream.writeAll("[*]");
+ try writer.writeAll("[*]");
ty = pointee_type;
continue;
},
.c_const_pointer => {
const pointee_type = ty.castTag(.c_const_pointer).?.data;
- try out_stream.writeAll("[*c]const ");
+ try writer.writeAll("[*c]const ");
ty = pointee_type;
continue;
},
.c_mut_pointer => {
const pointee_type = ty.castTag(.c_mut_pointer).?.data;
- try out_stream.writeAll("[*c]");
+ try writer.writeAll("[*c]");
ty = pointee_type;
continue;
},
.const_slice => {
const pointee_type = ty.castTag(.const_slice).?.data;
- try out_stream.writeAll("[]const ");
+ try writer.writeAll("[]const ");
ty = pointee_type;
continue;
},
.mut_slice => {
const pointee_type = ty.castTag(.mut_slice).?.data;
- try out_stream.writeAll("[]");
+ try writer.writeAll("[]");
ty = pointee_type;
continue;
},
.int_signed => {
const bits = ty.castTag(.int_signed).?.data;
- return out_stream.print("i{d}", .{bits});
+ return writer.print("i{d}", .{bits});
},
.int_unsigned => {
const bits = ty.castTag(.int_unsigned).?.data;
- return out_stream.print("u{d}", .{bits});
+ return writer.print("u{d}", .{bits});
},
.optional => {
const child_type = ty.castTag(.optional).?.data;
- try out_stream.writeByte('?');
+ try writer.writeByte('?');
ty = child_type;
continue;
},
.optional_single_const_pointer => {
const pointee_type = ty.castTag(.optional_single_const_pointer).?.data;
- try out_stream.writeAll("?*const ");
+ try writer.writeAll("?*const ");
ty = pointee_type;
continue;
},
.optional_single_mut_pointer => {
const pointee_type = ty.castTag(.optional_single_mut_pointer).?.data;
- try out_stream.writeAll("?*");
+ try writer.writeAll("?*");
ty = pointee_type;
continue;
},
@@ -804,48 +865,46 @@ pub const Type = extern union {
const payload = ty.castTag(.pointer).?.data;
if (payload.sentinel) |some| switch (payload.size) {
.One, .C => unreachable,
- .Many => try out_stream.print("[*:{}]", .{some}),
- .Slice => try out_stream.print("[:{}]", .{some}),
+ .Many => try writer.print("[*:{}]", .{some}),
+ .Slice => try writer.print("[:{}]", .{some}),
} else switch (payload.size) {
- .One => try out_stream.writeAll("*"),
- .Many => try out_stream.writeAll("[*]"),
- .C => try out_stream.writeAll("[*c]"),
- .Slice => try out_stream.writeAll("[]"),
+ .One => try writer.writeAll("*"),
+ .Many => try writer.writeAll("[*]"),
+ .C => try writer.writeAll("[*c]"),
+ .Slice => try writer.writeAll("[]"),
}
if (payload.@"align" != 0) {
- try out_stream.print("align({d}", .{payload.@"align"});
+ try writer.print("align({d}", .{payload.@"align"});
if (payload.bit_offset != 0) {
- try out_stream.print(":{d}:{d}", .{ payload.bit_offset, payload.host_size });
+ try writer.print(":{d}:{d}", .{ payload.bit_offset, payload.host_size });
}
- try out_stream.writeAll(") ");
+ try writer.writeAll(") ");
}
- if (!payload.mutable) try out_stream.writeAll("const ");
- if (payload.@"volatile") try out_stream.writeAll("volatile ");
- if (payload.@"allowzero") try out_stream.writeAll("allowzero ");
+ if (!payload.mutable) try writer.writeAll("const ");
+ if (payload.@"volatile") try writer.writeAll("volatile ");
+ if (payload.@"allowzero") try writer.writeAll("allowzero ");
ty = payload.pointee_type;
continue;
},
.error_union => {
const payload = ty.castTag(.error_union).?.data;
- try payload.error_set.format("", .{}, out_stream);
- try out_stream.writeAll("!");
+ try payload.error_set.format("", .{}, writer);
+ try writer.writeAll("!");
ty = payload.payload;
continue;
},
.error_set => {
const error_set = ty.castTag(.error_set).?.data;
- return out_stream.writeAll(std.mem.spanZ(error_set.owner_decl.name));
+ return writer.writeAll(std.mem.spanZ(error_set.owner_decl.name));
},
.error_set_single => {
const name = ty.castTag(.error_set_single).?.data;
- return out_stream.print("error{{{s}}}", .{name});
+ return writer.print("error{{{s}}}", .{name});
},
- .inferred_alloc_const => return out_stream.writeAll("(inferred_alloc_const)"),
- .inferred_alloc_mut => return out_stream.writeAll("(inferred_alloc_mut)"),
- // TODO use declaration name
- .@"opaque" => return out_stream.writeAll("opaque {}"),
+ .inferred_alloc_const => return writer.writeAll("(inferred_alloc_const)"),
+ .inferred_alloc_mut => return writer.writeAll("(inferred_alloc_mut)"),
}
unreachable;
}
@@ -954,6 +1013,19 @@ pub const Type = extern union {
return false;
}
},
+ .enum_full => {
+ const enum_full = self.castTag(.enum_full).?.data;
+ return enum_full.fields.count() >= 2;
+ },
+ .enum_simple => {
+ const enum_simple = self.castTag(.enum_simple).?.data;
+ return enum_simple.fields.count() >= 2;
+ },
+ .enum_nonexhaustive => {
+ var buffer: Payload.Bits = undefined;
+ const int_tag_ty = self.intTagType(&buffer);
+ return int_tag_ty.hasCodeGenBits();
+ },
// TODO lazy types
.array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0,
@@ -1112,13 +1184,37 @@ pub const Type = extern union {
} else if (!payload.payload.hasCodeGenBits()) {
return payload.error_set.abiAlignment(target);
}
- @panic("TODO abiAlignment error union");
+ return std.math.max(
+ payload.payload.abiAlignment(target),
+ payload.error_set.abiAlignment(target),
+ );
},
.@"struct" => {
- @panic("TODO abiAlignment struct");
+ // TODO take into account field alignment
+ // also make this possible to fail, and lazy
+ // I think we need to move all the functions from type.zig which can
+ // fail into Sema.
+ // Probably will need to introduce multi-stage struct resolution just
+ // like we have in stage1.
+ const struct_obj = self.castTag(.@"struct").?.data;
+ var biggest: u32 = 0;
+ for (struct_obj.fields.entries.items) |entry| {
+ const field_ty = entry.value.ty;
+ if (!field_ty.hasCodeGenBits()) continue;
+ const field_align = field_ty.abiAlignment(target);
+ if (field_align > biggest) {
+ return field_align;
+ }
+ }
+ assert(biggest != 0);
+ return biggest;
+ },
+ .enum_full, .enum_nonexhaustive, .enum_simple => {
+ var buffer: Payload.Bits = undefined;
+ const int_tag_ty = self.intTagType(&buffer);
+ return int_tag_ty.abiAlignment(target);
},
-
.c_void,
.void,
.type,
@@ -1166,6 +1262,11 @@ pub const Type = extern union {
.@"struct" => {
@panic("TODO abiSize struct");
},
+ .enum_simple, .enum_full, .enum_nonexhaustive => {
+ var buffer: Payload.Bits = undefined;
+ const int_tag_ty = self.intTagType(&buffer);
+ return int_tag_ty.abiSize(target);
+ },
.u8,
.i8,
@@ -1276,76 +1377,25 @@ pub const Type = extern union {
};
}
+ /// Asserts the type is an enum.
+ pub fn intTagType(self: Type, buffer: *Payload.Bits) Type {
+ switch (self.tag()) {
+ .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty,
+ .enum_simple => {
+ const enum_simple = self.castTag(.enum_simple).?.data;
+ const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count());
+ buffer.* = .{
+ .base = .{ .tag = .int_unsigned },
+ .data = bits,
+ };
+ return Type.initPayload(&buffer.base);
+ },
+ else => unreachable,
+ }
+ }
+
pub fn isSinglePointer(self: Type) bool {
return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .const_slice_u8,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .@"opaque",
- .var_args_param,
- => false,
-
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
@@ -1354,73 +1404,14 @@ pub const Type = extern union {
=> true,
.pointer => self.castTag(.pointer).?.data.size == .One,
+
+ else => false,
};
}
/// Asserts the `Type` is a pointer.
pub fn ptrSize(self: Type) std.builtin.TypeInfo.Pointer.Size {
return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .empty_struct,
- .empty_struct_literal,
- .@"opaque",
- .@"struct",
- .var_args_param,
- => unreachable,
-
.const_slice,
.mut_slice,
.const_slice_u8,
@@ -1442,159 +1433,26 @@ pub const Type = extern union {
=> .One,
.pointer => self.castTag(.pointer).?.data.size,
+
+ else => unreachable,
};
}
pub fn isSlice(self: Type) bool {
return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .single_const_pointer_to_comptime_int,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"struct",
- .@"opaque",
- .var_args_param,
- => false,
-
- .const_slice,
- .mut_slice,
- .const_slice_u8,
- => true,
-
- .pointer => self.castTag(.pointer).?.data.size == .Slice,
- };
- }
-
- pub fn isConstPtr(self: Type) bool {
- return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .int_unsigned,
- .int_signed,
- .single_mut_pointer,
- .many_mut_pointer,
- .c_mut_pointer,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .mut_slice,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"struct",
- .@"opaque",
- .var_args_param,
- => false,
-
+ .const_slice,
+ .mut_slice,
+ .const_slice_u8,
+ => true,
+
+ .pointer => self.castTag(.pointer).?.data.size == .Slice,
+
+ else => false,
+ };
+ }
+
+ pub fn isConstPtr(self: Type) bool {
+ return switch (self.tag()) {
.single_const_pointer,
.many_const_pointer,
.c_const_pointer,
@@ -1604,182 +1462,52 @@ pub const Type = extern union {
=> true,
.pointer => !self.castTag(.pointer).?.data.mutable,
+
+ else => false,
};
}
pub fn isVolatilePtr(self: Type) bool {
return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .int_unsigned,
- .int_signed,
- .single_mut_pointer,
- .single_const_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"struct",
- .@"opaque",
- .var_args_param,
- => false,
-
.pointer => {
const payload = self.castTag(.pointer).?.data;
return payload.@"volatile";
},
+ else => false,
};
}
pub fn isAllowzeroPtr(self: Type) bool {
return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .int_unsigned,
- .int_signed,
- .single_mut_pointer,
- .single_const_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"struct",
- .@"opaque",
- .var_args_param,
- => false,
-
.pointer => {
const payload = self.castTag(.pointer).?.data;
return payload.@"allowzero";
},
+ else => false,
};
}
- /// Asserts that the type is an optional
- pub fn isPtrLikeOptional(self: Type) bool {
- switch (self.tag()) {
- .optional_single_const_pointer, .optional_single_mut_pointer => return true,
- .optional => {
- var buf: Payload.ElemType = undefined;
- const child_type = self.optionalChild(&buf);
- // optionals of zero sized pointers behave like bools
- if (!child_type.hasCodeGenBits()) return false;
+ pub fn isCPtr(self: Type) bool {
+ return switch (self.tag()) {
+ .c_const_pointer,
+ .c_mut_pointer,
+ => return true,
+
+ .pointer => self.castTag(.pointer).?.data.size == .C,
+
+ else => return false,
+ };
+ }
+
+ /// Asserts that the type is an optional
+ pub fn isPtrLikeOptional(self: Type) bool {
+ switch (self.tag()) {
+ .optional_single_const_pointer, .optional_single_mut_pointer => return true,
+ .optional => {
+ var buf: Payload.ElemType = undefined;
+ const child_type = self.optionalChild(&buf);
+ // optionals of zero sized pointers behave like bools
+ if (!child_type.hasCodeGenBits()) return false;
return child_type.zigTypeTag() == .Pointer and !child_type.isCPtr();
},
@@ -1833,64 +1561,6 @@ pub const Type = extern union {
/// Asserts the type is a pointer or array type.
pub fn elemType(self: Type) Type {
return switch (self.tag()) {
- .u8 => unreachable,
- .i8 => unreachable,
- .u16 => unreachable,
- .i16 => unreachable,
- .u32 => unreachable,
- .i32 => unreachable,
- .u64 => unreachable,
- .i64 => unreachable,
- .u128 => unreachable,
- .i128 => unreachable,
- .usize => unreachable,
- .isize => unreachable,
- .c_short => unreachable,
- .c_ushort => unreachable,
- .c_int => unreachable,
- .c_uint => unreachable,
- .c_long => unreachable,
- .c_ulong => unreachable,
- .c_longlong => unreachable,
- .c_ulonglong => unreachable,
- .c_longdouble => unreachable,
- .f16 => unreachable,
- .f32 => unreachable,
- .f64 => unreachable,
- .f128 => unreachable,
- .c_void => unreachable,
- .bool => unreachable,
- .void => unreachable,
- .type => unreachable,
- .anyerror => unreachable,
- .comptime_int => unreachable,
- .comptime_float => unreachable,
- .noreturn => unreachable,
- .@"null" => unreachable,
- .@"undefined" => unreachable,
- .fn_noreturn_no_args => unreachable,
- .fn_void_no_args => unreachable,
- .fn_naked_noreturn_no_args => unreachable,
- .fn_ccc_void_no_args => unreachable,
- .function => unreachable,
- .int_unsigned => unreachable,
- .int_signed => unreachable,
- .optional => unreachable,
- .optional_single_const_pointer => unreachable,
- .optional_single_mut_pointer => unreachable,
- .enum_literal => unreachable,
- .error_union => unreachable,
- .anyerror_void_error_union => unreachable,
- .error_set => unreachable,
- .error_set_single => unreachable,
- .@"struct" => unreachable,
- .empty_struct => unreachable,
- .empty_struct_literal => unreachable,
- .inferred_alloc_const => unreachable,
- .inferred_alloc_mut => unreachable,
- .@"opaque" => unreachable,
- .var_args_param => unreachable,
-
.array => self.castTag(.array).?.data.elem_type,
.array_sentinel => self.castTag(.array_sentinel).?.data.elem_type,
.single_const_pointer,
@@ -1902,9 +1572,12 @@ pub const Type = extern union {
.const_slice,
.mut_slice,
=> self.castPointer().?.data,
+
.array_u8, .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8),
.single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
.pointer => self.castTag(.pointer).?.data.pointee_type,
+
+ else => unreachable,
};
}
@@ -1972,823 +1645,120 @@ pub const Type = extern union {
/// Asserts the type is an array or vector.
pub fn arrayLen(self: Type) u64 {
return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
-
.array => self.castTag(.array).?.data.len,
.array_sentinel => self.castTag(.array_sentinel).?.data.len,
.array_u8 => self.castTag(.array_u8).?.data,
.array_u8_sentinel_0 => self.castTag(.array_u8_sentinel_0).?.data,
- };
- }
-
- /// Asserts the type is an array, pointer or vector.
- pub fn sentinel(self: Type) ?Value {
- return switch (self.tag()) {
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .c_longdouble,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .const_slice,
- .mut_slice,
- .const_slice_u8,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .single_const_pointer_to_comptime_int,
- .array,
- .array_u8,
- => return null,
-
- .pointer => return self.castTag(.pointer).?.data.sentinel,
- .array_sentinel => return self.castTag(.array_sentinel).?.data.sentinel,
- .array_u8_sentinel_0 => return Value.initTag(.zero),
+ else => unreachable,
};
}
- /// Returns true if and only if the type is a fixed-width integer.
- pub fn isInt(self: Type) bool {
- return self.isSignedInt() or self.isUnsignedInt();
- }
-
- /// Returns true if and only if the type is a fixed-width, signed integer.
- pub fn isSignedInt(self: Type) bool {
- return switch (self.tag()) {
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .int_unsigned,
- .u8,
- .usize,
- .c_ushort,
- .c_uint,
- .c_ulong,
- .c_ulonglong,
- .u16,
- .u32,
- .u64,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => false,
-
- .int_signed,
- .i8,
- .isize,
- .c_short,
- .c_int,
- .c_long,
- .c_longlong,
- .i16,
- .i32,
- .i64,
- .u128,
- .i128,
- => true,
- };
- }
-
- /// Returns true if and only if the type is a fixed-width, unsigned integer.
- pub fn isUnsignedInt(self: Type) bool {
- return switch (self.tag()) {
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .int_signed,
- .i8,
- .isize,
- .c_short,
- .c_int,
- .c_long,
- .c_longlong,
- .i16,
- .i32,
- .i64,
- .u128,
- .i128,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => false,
-
- .int_unsigned,
- .u8,
- .usize,
- .c_ushort,
- .c_uint,
- .c_ulong,
- .c_ulonglong,
- .u16,
- .u32,
- .u64,
- => true,
- };
- }
-
- /// Asserts the type is an integer.
- pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } {
- return switch (self.tag()) {
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
-
- .int_unsigned => .{
- .signedness = .unsigned,
- .bits = self.castTag(.int_unsigned).?.data,
- },
- .int_signed => .{
- .signedness = .signed,
- .bits = self.castTag(.int_signed).?.data,
- },
- .u8 => .{ .signedness = .unsigned, .bits = 8 },
- .i8 => .{ .signedness = .signed, .bits = 8 },
- .u16 => .{ .signedness = .unsigned, .bits = 16 },
- .i16 => .{ .signedness = .signed, .bits = 16 },
- .u32 => .{ .signedness = .unsigned, .bits = 32 },
- .i32 => .{ .signedness = .signed, .bits = 32 },
- .u64 => .{ .signedness = .unsigned, .bits = 64 },
- .i64 => .{ .signedness = .signed, .bits = 64 },
- .u128 => .{ .signedness = .unsigned, .bits = 128 },
- .i128 => .{ .signedness = .signed, .bits = 128 },
- .usize => .{ .signedness = .unsigned, .bits = target.cpu.arch.ptrBitWidth() },
- .isize => .{ .signedness = .signed, .bits = target.cpu.arch.ptrBitWidth() },
- .c_short => .{ .signedness = .signed, .bits = CType.short.sizeInBits(target) },
- .c_ushort => .{ .signedness = .unsigned, .bits = CType.ushort.sizeInBits(target) },
- .c_int => .{ .signedness = .signed, .bits = CType.int.sizeInBits(target) },
- .c_uint => .{ .signedness = .unsigned, .bits = CType.uint.sizeInBits(target) },
- .c_long => .{ .signedness = .signed, .bits = CType.long.sizeInBits(target) },
- .c_ulong => .{ .signedness = .unsigned, .bits = CType.ulong.sizeInBits(target) },
- .c_longlong => .{ .signedness = .signed, .bits = CType.longlong.sizeInBits(target) },
- .c_ulonglong => .{ .signedness = .unsigned, .bits = CType.ulonglong.sizeInBits(target) },
- };
- }
-
- pub fn isNamedInt(self: Type) bool {
- return switch (self.tag()) {
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .int_unsigned,
- .int_signed,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => false,
-
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- => true,
- };
- }
-
- pub fn isFloat(self: Type) bool {
- return switch (self.tag()) {
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- => true,
-
- else => false,
- };
- }
-
- /// Asserts the type is a fixed-size float.
- pub fn floatBits(self: Type, target: Target) u16 {
- return switch (self.tag()) {
- .f16 => 16,
- .f32 => 32,
- .f64 => 64,
- .f128 => 128,
- .c_longdouble => CType.longdouble.sizeInBits(target),
-
- else => unreachable,
- };
- }
-
- /// Asserts the type is a function.
- pub fn fnParamLen(self: Type) usize {
- return switch (self.tag()) {
- .fn_noreturn_no_args => 0,
- .fn_void_no_args => 0,
- .fn_naked_noreturn_no_args => 0,
- .fn_ccc_void_no_args => 0,
- .function => self.castTag(.function).?.data.param_types.len,
-
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
- };
- }
-
- /// Asserts the type is a function. The length of the slice must be at least the length
- /// given by `fnParamLen`.
- pub fn fnParamTypes(self: Type, types: []Type) void {
- switch (self.tag()) {
- .fn_noreturn_no_args => return,
- .fn_void_no_args => return,
- .fn_naked_noreturn_no_args => return,
- .fn_ccc_void_no_args => return,
- .function => {
- const payload = self.castTag(.function).?.data;
- std.mem.copy(Type, types, payload.param_types);
- },
-
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
- }
- }
-
- /// Asserts the type is a function.
- pub fn fnParamType(self: Type, index: usize) Type {
- switch (self.tag()) {
- .function => {
- const payload = self.castTag(.function).?.data;
- return payload.param_types[index];
- },
-
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
+ /// Asserts the type is an array, pointer or vector.
+ pub fn sentinel(self: Type) ?Value {
+ return switch (self.tag()) {
.single_const_pointer,
.single_mut_pointer,
.many_const_pointer,
.many_mut_pointer,
.c_const_pointer,
.c_mut_pointer,
- .const_slice,
- .mut_slice,
.single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .u8,
+ .array,
+ .array_u8,
+ => return null,
+
+ .pointer => return self.castTag(.pointer).?.data.sentinel,
+ .array_sentinel => return self.castTag(.array_sentinel).?.data.sentinel,
+ .array_u8_sentinel_0 => return Value.initTag(.zero),
+
+ else => unreachable,
+ };
+ }
+
+ /// Returns true if and only if the type is a fixed-width integer.
+ pub fn isInt(self: Type) bool {
+ return self.isSignedInt() or self.isUnsignedInt();
+ }
+
+ /// Returns true if and only if the type is a fixed-width, signed integer.
+ pub fn isSignedInt(self: Type) bool {
+ return switch (self.tag()) {
+ .int_signed,
.i8,
- .u16,
+ .isize,
+ .c_short,
+ .c_int,
+ .c_long,
+ .c_longlong,
.i16,
- .u32,
.i32,
- .u64,
.i64,
- .u128,
.i128,
+ => true,
+
+ else => false,
+ };
+ }
+
+ /// Returns true if and only if the type is a fixed-width, unsigned integer.
+ pub fn isUnsignedInt(self: Type) bool {
+ return switch (self.tag()) {
+ .int_unsigned,
+ .u8,
.usize,
- .isize,
- .c_short,
.c_ushort,
- .c_int,
.c_uint,
- .c_long,
.c_ulong,
- .c_longlong,
.c_ulonglong,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
- }
+ .u16,
+ .u32,
+ .u64,
+ .u128,
+ => true,
+
+ else => false,
+ };
}
- /// Asserts the type is a function.
- pub fn fnReturnType(self: Type) Type {
+ /// Asserts the type is an integer.
+ pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } {
return switch (self.tag()) {
- .fn_noreturn_no_args => Type.initTag(.noreturn),
- .fn_naked_noreturn_no_args => Type.initTag(.noreturn),
-
- .fn_void_no_args,
- .fn_ccc_void_no_args,
- => Type.initTag(.void),
+ .int_unsigned => .{
+ .signedness = .unsigned,
+ .bits = self.castTag(.int_unsigned).?.data,
+ },
+ .int_signed => .{
+ .signedness = .signed,
+ .bits = self.castTag(.int_signed).?.data,
+ },
+ .u8 => .{ .signedness = .unsigned, .bits = 8 },
+ .i8 => .{ .signedness = .signed, .bits = 8 },
+ .u16 => .{ .signedness = .unsigned, .bits = 16 },
+ .i16 => .{ .signedness = .signed, .bits = 16 },
+ .u32 => .{ .signedness = .unsigned, .bits = 32 },
+ .i32 => .{ .signedness = .signed, .bits = 32 },
+ .u64 => .{ .signedness = .unsigned, .bits = 64 },
+ .i64 => .{ .signedness = .signed, .bits = 64 },
+ .u128 => .{ .signedness = .unsigned, .bits = 128 },
+ .i128 => .{ .signedness = .signed, .bits = 128 },
+ .usize => .{ .signedness = .unsigned, .bits = target.cpu.arch.ptrBitWidth() },
+ .isize => .{ .signedness = .signed, .bits = target.cpu.arch.ptrBitWidth() },
+ .c_short => .{ .signedness = .signed, .bits = CType.short.sizeInBits(target) },
+ .c_ushort => .{ .signedness = .unsigned, .bits = CType.ushort.sizeInBits(target) },
+ .c_int => .{ .signedness = .signed, .bits = CType.int.sizeInBits(target) },
+ .c_uint => .{ .signedness = .unsigned, .bits = CType.uint.sizeInBits(target) },
+ .c_long => .{ .signedness = .signed, .bits = CType.long.sizeInBits(target) },
+ .c_ulong => .{ .signedness = .unsigned, .bits = CType.ulong.sizeInBits(target) },
+ .c_longlong => .{ .signedness = .signed, .bits = CType.longlong.sizeInBits(target) },
+ .c_ulonglong => .{ .signedness = .unsigned, .bits = CType.ulonglong.sizeInBits(target) },
- .function => self.castTag(.function).?.data.return_type,
+ else => unreachable,
+ };
+ }
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
+ pub fn isNamedInt(self: Type) bool {
+ return switch (self.tag()) {
.usize,
.isize,
.c_short,
@@ -2799,24 +1769,93 @@ pub const Type = extern union {
.c_ulong,
.c_longlong,
.c_ulonglong,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
+ => true,
+
+ else => false,
+ };
+ }
+
+ pub fn isFloat(self: Type) bool {
+ return switch (self.tag()) {
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ => true,
+
+ else => false,
+ };
+ }
+
+ /// Asserts the type is a fixed-size float.
+ pub fn floatBits(self: Type, target: Target) u16 {
+ return switch (self.tag()) {
+ .f16 => 16,
+ .f32 => 32,
+ .f64 => 64,
+ .f128 => 128,
+ .c_longdouble => CType.longdouble.sizeInBits(target),
+
+ else => unreachable,
+ };
+ }
+
+ /// Asserts the type is a function.
+ pub fn fnParamLen(self: Type) usize {
+ return switch (self.tag()) {
+ .fn_noreturn_no_args => 0,
+ .fn_void_no_args => 0,
+ .fn_naked_noreturn_no_args => 0,
+ .fn_ccc_void_no_args => 0,
+ .function => self.castTag(.function).?.data.param_types.len,
+
+ else => unreachable,
+ };
+ }
+
+ /// Asserts the type is a function. The length of the slice must be at least the length
+ /// given by `fnParamLen`.
+ pub fn fnParamTypes(self: Type, types: []Type) void {
+ switch (self.tag()) {
+ .fn_noreturn_no_args => return,
+ .fn_void_no_args => return,
+ .fn_naked_noreturn_no_args => return,
+ .fn_ccc_void_no_args => return,
+ .function => {
+ const payload = self.castTag(.function).?.data;
+ std.mem.copy(Type, types, payload.param_types);
+ },
+
+ else => unreachable,
+ }
+ }
+
+ /// Asserts the type is a function.
+ pub fn fnParamType(self: Type, index: usize) Type {
+ switch (self.tag()) {
+ .function => {
+ const payload = self.castTag(.function).?.data;
+ return payload.param_types[index];
+ },
+
+ else => unreachable,
+ }
+ }
+
+ /// Asserts the type is a function.
+ pub fn fnReturnType(self: Type) Type {
+ return switch (self.tag()) {
+ .fn_noreturn_no_args => Type.initTag(.noreturn),
+ .fn_naked_noreturn_no_args => Type.initTag(.noreturn),
+
+ .fn_void_no_args,
+ .fn_ccc_void_no_args,
+ => Type.initTag(.void),
+
+ .function => self.castTag(.function).?.data.return_type,
+
+ else => unreachable,
};
}
@@ -2829,74 +1868,7 @@ pub const Type = extern union {
.fn_ccc_void_no_args => .C,
.function => self.castTag(.function).?.data.cc,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
+ else => unreachable,
};
}
@@ -2909,74 +1881,7 @@ pub const Type = extern union {
.fn_ccc_void_no_args => false,
.function => self.castTag(.function).?.data.is_var_args,
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .comptime_int,
- .comptime_float,
- .noreturn,
- .@"null",
- .@"undefined",
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .int_unsigned,
- .int_signed,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => unreachable,
+ else => unreachable,
};
}
@@ -2984,84 +1889,41 @@ pub const Type = extern union {
return switch (self.tag()) {
.f16,
.f32,
- .f64,
- .f128,
- .c_longdouble,
- .comptime_int,
- .comptime_float,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .int_unsigned,
- .int_signed,
- => true,
-
- .c_void,
- .bool,
- .void,
- .type,
- .anyerror,
- .noreturn,
- .@"null",
- .@"undefined",
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .pointer,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .const_slice,
- .mut_slice,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => false,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .comptime_int,
+ .comptime_float,
+ .u8,
+ .i8,
+ .u16,
+ .i16,
+ .u32,
+ .i32,
+ .u64,
+ .i64,
+ .u128,
+ .i128,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .int_unsigned,
+ .int_signed,
+ => true,
+
+ else => false,
};
}
- pub fn onePossibleValue(self: Type) ?Value {
- var ty = self;
+ pub fn onePossibleValue(starting_type: Type) ?Value {
+ var ty = starting_type;
while (true) switch (ty.tag()) {
.f16,
.f32,
@@ -3118,9 +1980,32 @@ pub const Type = extern union {
=> return null,
.@"struct" => {
- log.warn("TODO implement Type.onePossibleValue for structs", .{});
- return null;
+ const s = ty.castTag(.@"struct").?.data;
+ for (s.fields.entries.items) |entry| {
+ const field_ty = entry.value.ty;
+ if (field_ty.onePossibleValue() == null) {
+ return null;
+ }
+ }
+ return Value.initTag(.empty_struct_value);
+ },
+ .enum_full => {
+ const enum_full = ty.castTag(.enum_full).?.data;
+ if (enum_full.fields.count() == 1) {
+ return enum_full.values.entries.items[0].key;
+ } else {
+ return null;
+ }
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ if (enum_simple.fields.count() == 1) {
+ return Value.initTag(.zero);
+ } else {
+ return null;
+ }
},
+ .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty,
.empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value),
.void => return Value.initTag(.void_value),
@@ -3160,87 +2045,6 @@ pub const Type = extern union {
};
}
- pub fn isCPtr(self: Type) bool {
- return switch (self.tag()) {
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .comptime_int,
- .comptime_float,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .bool,
- .type,
- .anyerror,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .c_void,
- .void,
- .noreturn,
- .@"null",
- .@"undefined",
- .int_unsigned,
- .int_signed,
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .const_slice,
- .mut_slice,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .@"struct",
- .empty_struct,
- .empty_struct_literal,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .@"opaque",
- .var_args_param,
- => return false,
-
- .c_const_pointer,
- .c_mut_pointer,
- => return true,
-
- .pointer => self.castTag(.pointer).?.data.size == .C,
- };
- }
-
pub fn isIndexable(self: Type) bool {
const zig_tag = self.zigTypeTag();
// TODO tuples are indexable
@@ -3248,83 +2052,15 @@ pub const Type = extern union {
(self.isSinglePointer() and self.elemType().zigTypeTag() == .Array);
}
- /// Asserts that the type is a container. (note: ErrorSet is not a container).
- pub fn getContainerScope(self: Type) *Module.Scope.Container {
+ /// Returns null if the type has no container.
+ pub fn getContainerScope(self: Type) ?*Module.Scope.Container {
return switch (self.tag()) {
- .f16,
- .f32,
- .f64,
- .f128,
- .c_longdouble,
- .comptime_int,
- .comptime_float,
- .u8,
- .i8,
- .u16,
- .i16,
- .u32,
- .i32,
- .u64,
- .i64,
- .u128,
- .i128,
- .usize,
- .isize,
- .c_short,
- .c_ushort,
- .c_int,
- .c_uint,
- .c_long,
- .c_ulong,
- .c_longlong,
- .c_ulonglong,
- .bool,
- .type,
- .anyerror,
- .fn_noreturn_no_args,
- .fn_void_no_args,
- .fn_naked_noreturn_no_args,
- .fn_ccc_void_no_args,
- .function,
- .single_const_pointer_to_comptime_int,
- .const_slice_u8,
- .c_void,
- .void,
- .noreturn,
- .@"null",
- .@"undefined",
- .int_unsigned,
- .int_signed,
- .array,
- .array_sentinel,
- .array_u8,
- .array_u8_sentinel_0,
- .single_const_pointer,
- .single_mut_pointer,
- .many_const_pointer,
- .many_mut_pointer,
- .const_slice,
- .mut_slice,
- .optional,
- .optional_single_mut_pointer,
- .optional_single_const_pointer,
- .enum_literal,
- .error_union,
- .anyerror_void_error_union,
- .error_set,
- .error_set_single,
- .c_const_pointer,
- .c_mut_pointer,
- .pointer,
- .inferred_alloc_const,
- .inferred_alloc_mut,
- .var_args_param,
- .empty_struct_literal,
- => unreachable,
-
.@"struct" => &self.castTag(.@"struct").?.data.container,
+ .enum_full => &self.castTag(.enum_full).?.data.container,
.empty_struct => self.castTag(.empty_struct).?.data,
.@"opaque" => &self.castTag(.@"opaque").?.data,
+
+ else => null,
};
}
@@ -3383,8 +2119,144 @@ pub const Type = extern union {
}
}
- pub fn isExhaustiveEnum(ty: Type) bool {
- return false; // TODO
+ pub fn isNonexhaustiveEnum(ty: Type) bool {
+ return switch (ty.tag()) {
+ .enum_nonexhaustive => true,
+ else => false,
+ };
+ }
+
+ pub fn enumFieldCount(ty: Type) usize {
+ switch (ty.tag()) {
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Payload.EnumFull).?.data;
+ return enum_full.fields.count();
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ return enum_simple.fields.count();
+ },
+ else => unreachable,
+ }
+ }
+
+ pub fn enumFieldName(ty: Type, field_index: usize) []const u8 {
+ switch (ty.tag()) {
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Payload.EnumFull).?.data;
+ return enum_full.fields.entries.items[field_index].key;
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ return enum_simple.fields.entries.items[field_index].key;
+ },
+ else => unreachable,
+ }
+ }
+
+ pub fn enumFieldIndex(ty: Type, field_name: []const u8) ?usize {
+ switch (ty.tag()) {
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Payload.EnumFull).?.data;
+ return enum_full.fields.getIndex(field_name);
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ return enum_simple.fields.getIndex(field_name);
+ },
+ else => unreachable,
+ }
+ }
+
+ /// Asserts `ty` is an enum. `enum_tag` can either be `enum_field_index` or
+ /// an integer which represents the enum value. Returns the field index in
+ /// declaration order, or `null` if `enum_tag` does not match any field.
+ pub fn enumTagFieldIndex(ty: Type, enum_tag: Value) ?usize {
+ if (enum_tag.castTag(.enum_field_index)) |payload| {
+ return @as(usize, payload.data);
+ }
+ const S = struct {
+ fn fieldWithRange(int_val: Value, end: usize) ?usize {
+ if (int_val.compareWithZero(.lt)) return null;
+ var end_payload: Value.Payload.U64 = .{
+ .base = .{ .tag = .int_u64 },
+ .data = end,
+ };
+ const end_val = Value.initPayload(&end_payload.base);
+ if (int_val.compare(.gte, end_val)) return null;
+ return int_val.toUnsignedInt();
+ }
+ };
+ switch (ty.tag()) {
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Payload.EnumFull).?.data;
+ if (enum_full.values.count() == 0) {
+ return S.fieldWithRange(enum_tag, enum_full.fields.count());
+ } else {
+ return enum_full.values.getIndex(enum_tag);
+ }
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ return S.fieldWithRange(enum_tag, enum_simple.fields.count());
+ },
+ else => unreachable,
+ }
+ }
+
+ pub fn declSrcLoc(ty: Type) Module.SrcLoc {
+ switch (ty.tag()) {
+ .enum_full, .enum_nonexhaustive => {
+ const enum_full = ty.cast(Payload.EnumFull).?.data;
+ return enum_full.srcLoc();
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ return enum_simple.srcLoc();
+ },
+ .@"struct" => {
+ const struct_obj = ty.castTag(.@"struct").?.data;
+ return struct_obj.srcLoc();
+ },
+ .error_set => {
+ const error_set = ty.castTag(.error_set).?.data;
+ return error_set.srcLoc();
+ },
+ else => unreachable,
+ }
+ }
+
+ /// Asserts the type is an enum.
+ pub fn enumHasInt(ty: Type, int: Value, target: Target) bool {
+ const S = struct {
+ fn intInRange(int_val: Value, end: usize) bool {
+ if (int_val.compareWithZero(.lt)) return false;
+ var end_payload: Value.Payload.U64 = .{
+ .base = .{ .tag = .int_u64 },
+ .data = end,
+ };
+ const end_val = Value.initPayload(&end_payload.base);
+ if (int_val.compare(.gte, end_val)) return false;
+ return true;
+ }
+ };
+ switch (ty.tag()) {
+ .enum_nonexhaustive => return int.intFitsInType(ty, target),
+ .enum_full => {
+ const enum_full = ty.castTag(.enum_full).?.data;
+ if (enum_full.values.count() == 0) {
+ return S.intInRange(int, enum_full.fields.count());
+ } else {
+ return enum_full.values.contains(int);
+ }
+ },
+ .enum_simple => {
+ const enum_simple = ty.castTag(.enum_simple).?.data;
+ return S.intInRange(int, enum_simple.fields.count());
+ },
+
+ else => unreachable,
+ }
}
/// This enum does not directly correspond to `std.builtin.TypeId` because
@@ -3476,6 +2348,9 @@ pub const Type = extern union {
empty_struct,
@"opaque",
@"struct",
+ enum_simple,
+ enum_full,
+ enum_nonexhaustive,
pub const last_no_payload_tag = Tag.inferred_alloc_const;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@@ -3562,6 +2437,8 @@ pub const Type = extern union {
.error_set_single => Payload.Name,
.@"opaque" => Payload.Opaque,
.@"struct" => Payload.Struct,
+ .enum_full, .enum_nonexhaustive => Payload.EnumFull,
+ .enum_simple => Payload.EnumSimple,
.empty_struct => Payload.ContainerScope,
};
}
@@ -3699,6 +2576,16 @@ pub const Type = extern union {
base: Payload = .{ .tag = .@"struct" },
data: *Module.Struct,
};
+
+ pub const EnumFull = struct {
+ base: Payload,
+ data: *Module.EnumFull,
+ };
+
+ pub const EnumSimple = struct {
+ base: Payload = .{ .tag = .enum_simple },
+ data: *Module.EnumSimple,
+ };
};
};
diff --git a/src/value.zig b/src/value.zig
@@ -103,6 +103,8 @@ pub const Value = extern union {
float_64,
float_128,
enum_literal,
+ /// A specific enum tag, indicated by the field index (declaration order).
+ enum_field_index,
@"error",
error_union,
/// This is a special value that tracks a set of types that have been stored
@@ -186,6 +188,8 @@ pub const Value = extern union {
.enum_literal,
=> Payload.Bytes,
+ .enum_field_index => Payload.U32,
+
.ty => Payload.Ty,
.int_type => Payload.IntType,
.int_u64 => Payload.U64,
@@ -394,6 +398,7 @@ pub const Value = extern union {
};
return Value{ .ptr_otherwise = &new_payload.base };
},
+ .enum_field_index => return self.copyPayloadShallow(allocator, Payload.U32),
.@"error" => return self.copyPayloadShallow(allocator, Payload.Error),
.error_union => {
const payload = self.castTag(.error_union).?;
@@ -416,6 +421,8 @@ pub const Value = extern union {
return Value{ .ptr_otherwise = &new_payload.base };
}
+ /// TODO this should become a debug dump() function. In order to print values in a meaningful way
+ /// we also need access to the type.
pub fn format(
self: Value,
comptime fmt: []const u8,
@@ -506,6 +513,7 @@ pub const Value = extern union {
},
.empty_array => return out_stream.writeAll(".{}"),
.enum_literal => return out_stream.print(".{}", .{std.zig.fmtId(self.castTag(.enum_literal).?.data)}),
+ .enum_field_index => return out_stream.print("(enum field {d})", .{self.castTag(.enum_field_index).?.data}),
.bytes => return out_stream.print("\"{}\"", .{std.zig.fmtEscapes(self.castTag(.bytes).?.data)}),
.repeated => {
try out_stream.writeAll("(repeated) ");
@@ -626,6 +634,7 @@ pub const Value = extern union {
.float_64,
.float_128,
.enum_literal,
+ .enum_field_index,
.@"error",
.error_union,
.empty_struct_value,
@@ -638,76 +647,6 @@ pub const Value = extern union {
/// Asserts the value is an integer.
pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst {
switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .unreachable_value,
- .empty_array,
- .enum_literal,
- .error_union,
- .@"error",
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
- .undef => unreachable,
-
.zero,
.bool_false,
=> return BigIntMutable.init(&space.limbs, 0).toConst(),
@@ -720,82 +659,15 @@ pub const Value = extern union {
.int_i64 => return BigIntMutable.init(&space.limbs, self.castTag(.int_i64).?.data).toConst(),
.int_big_positive => return self.castTag(.int_big_positive).?.asBigInt(),
.int_big_negative => return self.castTag(.int_big_negative).?.asBigInt(),
+
+ .undef => unreachable,
+ else => unreachable,
}
}
/// Asserts the value is an integer and it fits in a u64
pub fn toUnsignedInt(self: Value) u64 {
switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .unreachable_value,
- .empty_array,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
- .undef => unreachable,
-
.zero,
.bool_false,
=> return 0,
@@ -808,82 +680,15 @@ pub const Value = extern union {
.int_i64 => return @intCast(u64, self.castTag(.int_i64).?.data),
.int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(u64) catch unreachable,
.int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(u64) catch unreachable,
+
+ .undef => unreachable,
+ else => unreachable,
}
}
/// Asserts the value is an integer and it fits in a i64
pub fn toSignedInt(self: Value) i64 {
switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .unreachable_value,
- .empty_array,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
- .undef => unreachable,
-
.zero,
.bool_false,
=> return 0,
@@ -896,6 +701,9 @@ pub const Value = extern union {
.int_i64 => return self.castTag(.int_i64).?.data,
.int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(i64) catch unreachable,
.int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(i64) catch unreachable,
+
+ .undef => unreachable,
+ else => unreachable,
}
}
@@ -929,75 +737,6 @@ pub const Value = extern union {
/// Returns the number of bits the value requires to represent stored in twos complement form.
pub fn intBitCountTwosComp(self: Value) usize {
switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .undef,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .unreachable_value,
- .empty_array,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
.zero,
.bool_false,
=> return 0,
@@ -1016,80 +755,14 @@ pub const Value = extern union {
},
.int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().bitCountTwosComp(),
.int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().bitCountTwosComp(),
+
+ else => unreachable,
}
}
/// Asserts the value is an integer, and the destination type is ComptimeInt or Int.
pub fn intFitsInType(self: Value, ty: Type, target: Target) bool {
switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .unreachable_value,
- .empty_array,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
.zero,
.undef,
.bool_false,
@@ -1144,6 +817,8 @@ pub const Value = extern union {
.ComptimeInt => return true,
else => unreachable,
},
+
+ else => unreachable,
}
}
@@ -1180,77 +855,6 @@ pub const Value = extern union {
/// Asserts the value is a float
pub fn floatHasFraction(self: Value) bool {
return switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .bool_true,
- .bool_false,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .undef,
- .int_u64,
- .int_i64,
- .int_big_positive,
- .int_big_negative,
- .empty_array,
- .void_value,
- .unreachable_value,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
.zero,
.one,
=> false,
@@ -1260,76 +864,13 @@ pub const Value = extern union {
.float_64 => @rem(self.castTag(.float_64).?.data, 1) != 0,
// .float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0,
.float_128 => @panic("TODO lld: error: undefined symbol: fmodl"),
+
+ else => unreachable,
};
}
pub fn orderAgainstZero(lhs: Value) std.math.Order {
return switch (lhs.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .undef,
- .void_value,
- .unreachable_value,
- .empty_array,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
.zero,
.bool_false,
=> .eq,
@@ -1347,6 +888,8 @@ pub const Value = extern union {
.float_32 => std.math.order(lhs.castTag(.float_32).?.data, 0),
.float_64 => std.math.order(lhs.castTag(.float_64).?.data, 0),
.float_128 => std.math.order(lhs.castTag(.float_128).?.data, 0),
+
+ else => unreachable,
};
}
@@ -1396,10 +939,12 @@ pub const Value = extern union {
}
pub fn eql(a: Value, b: Value) bool {
- if (a.tag() == b.tag()) {
- if (a.tag() == .void_value or a.tag() == .null_value) {
+ const a_tag = a.tag();
+ const b_tag = b.tag();
+ if (a_tag == b_tag) {
+ if (a_tag == .void_value or a_tag == .null_value) {
return true;
- } else if (a.tag() == .enum_literal) {
+ } else if (a_tag == .enum_literal) {
const a_name = a.castTag(.enum_literal).?.data;
const b_name = b.castTag(.enum_literal).?.data;
return std.mem.eql(u8, a_name, b_name);
@@ -1416,6 +961,10 @@ pub const Value = extern union {
return compare(a, .eq, b);
}
+ pub fn hash_u32(self: Value) u32 {
+ return @truncate(u32, self.hash());
+ }
+
pub fn hash(self: Value) u64 {
var hasher = std.hash.Wyhash.init(0);
@@ -1493,11 +1042,18 @@ pub const Value = extern union {
.zero, .bool_false => std.hash.autoHash(&hasher, @as(u64, 0)),
.one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)),
- .float_16, .float_32, .float_64, .float_128 => {},
+ .float_16, .float_32, .float_64, .float_128 => {
+ @panic("TODO implement Value.hash for floats");
+ },
+
.enum_literal => {
const payload = self.castTag(.enum_literal).?;
hasher.update(payload.data);
},
+ .enum_field_index => {
+ const payload = self.castTag(.enum_field_index).?;
+ std.hash.autoHash(&hasher, payload.data);
+ },
.bytes => {
const payload = self.castTag(.bytes).?;
hasher.update(payload.data);
@@ -1573,80 +1129,6 @@ pub const Value = extern union {
/// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
return switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .zero,
- .one,
- .bool_true,
- .bool_false,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .int_u64,
- .int_i64,
- .int_big_positive,
- .int_big_negative,
- .bytes,
- .undef,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .unreachable_value,
- .empty_array,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
.ref_val => self.castTag(.ref_val).?.data,
.decl_ref => self.castTag(.decl_ref).?.data.value(),
.elem_ptr => {
@@ -1654,6 +1136,8 @@ pub const Value = extern union {
const array_val = try elem_ptr.array_ptr.pointerDeref(allocator);
return array_val.elemValue(allocator, elem_ptr.index);
},
+
+ else => unreachable,
};
}
@@ -1661,86 +1145,14 @@ pub const Value = extern union {
/// or an unknown-length pointer, and returns the element value at the index.
pub fn elemValue(self: Value, allocator: *Allocator, index: usize) error{OutOfMemory}!Value {
switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .zero,
- .one,
- .bool_true,
- .bool_false,
- .null_value,
- .function,
- .extern_fn,
- .variable,
- .int_u64,
- .int_i64,
- .int_big_positive,
- .int_big_negative,
- .undef,
- .elem_ptr,
- .ref_val,
- .decl_ref,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .unreachable_value,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .inferred_alloc,
- .abi_align_default,
- => unreachable,
-
.empty_array => unreachable, // out of bounds array index
.bytes => return Tag.int_u64.create(allocator, self.castTag(.bytes).?.data[index]),
// No matter the index; all the elements are the same!
.repeated => return self.castTag(.repeated).?.data,
+
+ else => unreachable,
}
}
@@ -1766,161 +1178,18 @@ pub const Value = extern union {
/// Valid for all types. Asserts the value is not undefined and not unreachable.
pub fn isNull(self: Value) bool {
return switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .zero,
- .one,
- .empty_array,
- .bool_true,
- .bool_false,
- .function,
- .extern_fn,
- .variable,
- .int_u64,
- .int_i64,
- .int_big_positive,
- .int_big_negative,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .enum_literal,
- .@"error",
- .error_union,
- .empty_struct_value,
- .abi_align_default,
- => false,
-
.undef => unreachable,
.unreachable_value => unreachable,
.inferred_alloc => unreachable,
.null_value => true,
+
+ else => false,
};
}
/// Valid for all types. Asserts the value is not undefined and not unreachable.
pub fn getError(self: Value) ?[]const u8 {
return switch (self.tag()) {
- .ty,
- .int_type,
- .u8_type,
- .i8_type,
- .u16_type,
- .i16_type,
- .u32_type,
- .i32_type,
- .u64_type,
- .i64_type,
- .u128_type,
- .i128_type,
- .usize_type,
- .isize_type,
- .c_short_type,
- .c_ushort_type,
- .c_int_type,
- .c_uint_type,
- .c_long_type,
- .c_ulong_type,
- .c_longlong_type,
- .c_ulonglong_type,
- .c_longdouble_type,
- .f16_type,
- .f32_type,
- .f64_type,
- .f128_type,
- .c_void_type,
- .bool_type,
- .void_type,
- .type_type,
- .anyerror_type,
- .comptime_int_type,
- .comptime_float_type,
- .noreturn_type,
- .null_type,
- .undefined_type,
- .fn_noreturn_no_args_type,
- .fn_void_no_args_type,
- .fn_naked_noreturn_no_args_type,
- .fn_ccc_void_no_args_type,
- .single_const_pointer_to_comptime_int_type,
- .const_slice_u8_type,
- .enum_literal_type,
- .zero,
- .one,
- .null_value,
- .empty_array,
- .bool_true,
- .bool_false,
- .function,
- .extern_fn,
- .variable,
- .int_u64,
- .int_i64,
- .int_big_positive,
- .int_big_negative,
- .ref_val,
- .decl_ref,
- .elem_ptr,
- .bytes,
- .repeated,
- .float_16,
- .float_32,
- .float_64,
- .float_128,
- .void_value,
- .enum_literal,
- .empty_struct_value,
- .abi_align_default,
- => null,
-
.error_union => {
const data = self.castTag(.error_union).?.data;
return if (data.tag() == .@"error")
@@ -1932,6 +1201,8 @@ pub const Value = extern union {
.undef => unreachable,
.unreachable_value => unreachable,
.inferred_alloc => unreachable,
+
+ else => null,
};
}
/// Valid for all types. Asserts the value is not undefined.
@@ -2021,6 +1292,7 @@ pub const Value = extern union {
.float_128,
.void_value,
.enum_literal,
+ .enum_field_index,
.@"error",
.error_union,
.empty_struct_value,
@@ -2038,6 +1310,11 @@ pub const Value = extern union {
pub const Payload = struct {
tag: Tag,
+ pub const U32 = struct {
+ base: Payload,
+ data: u32,
+ };
+
pub const U64 = struct {
base: Payload,
data: u64,
diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp
@@ -2060,6 +2060,11 @@ bool ZigClangType_isRecordType(const ZigClangType *self) {
return casted->isRecordType();
}
+bool ZigClangType_isVectorType(const ZigClangType *self) {
+ auto casted = reinterpret_cast<const clang::Type *>(self);
+ return casted->isVectorType();
+}
+
bool ZigClangType_isIncompleteOrZeroLengthArrayType(const ZigClangQualType *self,
const struct ZigClangASTContext *ctx)
{
@@ -2752,6 +2757,16 @@ struct ZigClangQualType ZigClangBinaryOperator_getType(const struct ZigClangBina
return bitcast(casted->getType());
}
+const struct ZigClangExpr *ZigClangConvertVectorExpr_getSrcExpr(const struct ZigClangConvertVectorExpr *self) {
+ auto casted = reinterpret_cast<const clang::ConvertVectorExpr *>(self);
+ return reinterpret_cast<const struct ZigClangExpr *>(casted->getSrcExpr());
+}
+
+struct ZigClangQualType ZigClangConvertVectorExpr_getTypeSourceInfo_getType(const struct ZigClangConvertVectorExpr *self) {
+ auto casted = reinterpret_cast<const clang::ConvertVectorExpr *>(self);
+ return bitcast(casted->getTypeSourceInfo()->getType());
+}
+
struct ZigClangQualType ZigClangDecayedType_getDecayedType(const struct ZigClangDecayedType *self) {
auto casted = reinterpret_cast<const clang::DecayedType *>(self);
return bitcast(casted->getDecayedType());
@@ -2857,6 +2872,16 @@ struct ZigClangQualType ZigClangValueDecl_getType(const struct ZigClangValueDecl
return bitcast(casted->getType());
}
+struct ZigClangQualType ZigClangVectorType_getElementType(const struct ZigClangVectorType *self) {
+ auto casted = reinterpret_cast<const clang::VectorType *>(self);
+ return bitcast(casted->getElementType());
+}
+
+unsigned ZigClangVectorType_getNumElements(const struct ZigClangVectorType *self) {
+ auto casted = reinterpret_cast<const clang::VectorType *>(self);
+ return casted->getNumElements();
+}
+
const struct ZigClangExpr *ZigClangWhileStmt_getCond(const struct ZigClangWhileStmt *self) {
auto casted = reinterpret_cast<const clang::WhileStmt *>(self);
return reinterpret_cast<const struct ZigClangExpr *>(casted->getCond());
@@ -2936,6 +2961,15 @@ struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(
return bitcast(casted->getBeginLoc());
}
+unsigned ZigClangShuffleVectorExpr_getNumSubExprs(const ZigClangShuffleVectorExpr *self) {
+ auto casted = reinterpret_cast<const clang::ShuffleVectorExpr *>(self);
+ return casted->getNumSubExprs();
+}
+
+const struct ZigClangExpr *ZigClangShuffleVectorExpr_getExpr(const struct ZigClangShuffleVectorExpr *self, unsigned idx) {
+ auto casted = reinterpret_cast<const clang::ShuffleVectorExpr *>(self);
+ return reinterpret_cast<const struct ZigClangExpr *>(casted->getExpr(idx));
+}
enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind(
const struct ZigClangUnaryExprOrTypeTraitExpr *self)
diff --git a/src/zig_clang.h b/src/zig_clang.h
@@ -1071,6 +1071,7 @@ ZIG_EXTERN_C bool ZigClangType_isBooleanType(const struct ZigClangType *self);
ZIG_EXTERN_C bool ZigClangType_isVoidType(const struct ZigClangType *self);
ZIG_EXTERN_C bool ZigClangType_isArrayType(const struct ZigClangType *self);
ZIG_EXTERN_C bool ZigClangType_isRecordType(const struct ZigClangType *self);
+ZIG_EXTERN_C bool ZigClangType_isVectorType(const struct ZigClangType *self);
ZIG_EXTERN_C bool ZigClangType_isIncompleteOrZeroLengthArrayType(const ZigClangQualType *self, const struct ZigClangASTContext *ctx);
ZIG_EXTERN_C bool ZigClangType_isConstantArrayType(const ZigClangType *self);
ZIG_EXTERN_C const char *ZigClangType_getTypeClassName(const struct ZigClangType *self);
@@ -1206,6 +1207,9 @@ ZIG_EXTERN_C const struct ZigClangExpr *ZigClangBinaryOperator_getLHS(const stru
ZIG_EXTERN_C const struct ZigClangExpr *ZigClangBinaryOperator_getRHS(const struct ZigClangBinaryOperator *);
ZIG_EXTERN_C struct ZigClangQualType ZigClangBinaryOperator_getType(const struct ZigClangBinaryOperator *);
+ZIG_EXTERN_C const struct ZigClangExpr *ZigClangConvertVectorExpr_getSrcExpr(const struct ZigClangConvertVectorExpr *);
+ZIG_EXTERN_C struct ZigClangQualType ZigClangConvertVectorExpr_getTypeSourceInfo_getType(const struct ZigClangConvertVectorExpr *);
+
ZIG_EXTERN_C struct ZigClangQualType ZigClangDecayedType_getDecayedType(const struct ZigClangDecayedType *);
ZIG_EXTERN_C const struct ZigClangCompoundStmt *ZigClangStmtExpr_getSubStmt(const struct ZigClangStmtExpr *);
@@ -1235,6 +1239,9 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangUnaryOperator_getBeginLoc(con
ZIG_EXTERN_C struct ZigClangQualType ZigClangValueDecl_getType(const struct ZigClangValueDecl *);
+ZIG_EXTERN_C struct ZigClangQualType ZigClangVectorType_getElementType(const struct ZigClangVectorType *);
+ZIG_EXTERN_C unsigned ZigClangVectorType_getNumElements(const struct ZigClangVectorType *);
+
ZIG_EXTERN_C const struct ZigClangExpr *ZigClangWhileStmt_getCond(const struct ZigClangWhileStmt *);
ZIG_EXTERN_C const struct ZigClangStmt *ZigClangWhileStmt_getBody(const struct ZigClangWhileStmt *);
@@ -1259,6 +1266,9 @@ ZIG_EXTERN_C struct ZigClangQualType ZigClangUnaryExprOrTypeTraitExpr_getTypeOfA
ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(const struct ZigClangUnaryExprOrTypeTraitExpr *);
ZIG_EXTERN_C enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind(const struct ZigClangUnaryExprOrTypeTraitExpr *);
+ZIG_EXTERN_C unsigned ZigClangShuffleVectorExpr_getNumSubExprs(const struct ZigClangShuffleVectorExpr *);
+ZIG_EXTERN_C const struct ZigClangExpr *ZigClangShuffleVectorExpr_getExpr(const struct ZigClangShuffleVectorExpr *, unsigned);
+
ZIG_EXTERN_C const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *);
ZIG_EXTERN_C const struct ZigClangExpr *ZigClangDoStmt_getCond(const struct ZigClangDoStmt *);
diff --git a/src/zir.zig b/src/zir.zig
@@ -37,8 +37,6 @@ pub const Code = struct {
string_bytes: []u8,
/// The meaning of this data is determined by `Inst.Tag` value.
extra: []u32,
- /// Used for decl_val and decl_ref instructions.
- decls: []*Module.Decl,
/// Returns the requested data, as well as the new index which is at the start of the
/// trailers for the object.
@@ -78,7 +76,6 @@ pub const Code = struct {
code.instructions.deinit(gpa);
gpa.free(code.string_bytes);
gpa.free(code.extra);
- gpa.free(code.decls);
code.* = undefined;
}
@@ -133,7 +130,7 @@ pub const Inst = struct {
/// Same as `alloc` except mutable.
alloc_mut,
/// Same as `alloc` except the type is inferred.
- /// The operand is unused.
+ /// Uses the `node` union field.
alloc_inferred,
/// Same as `alloc_inferred` except mutable.
alloc_inferred_mut,
@@ -267,9 +264,6 @@ pub const Inst = struct {
/// only the taken branch is analyzed. The then block and else block must
/// terminate with an "inline" variant of a noreturn instruction.
condbr_inline,
- /// A comptime known value.
- /// Uses the `const` union field.
- @"const",
/// A struct type definition. Contains references to ZIR instructions for
/// the field types, defaults, and alignments.
/// Uses the `pl_node` union field. Payload is `StructDecl`.
@@ -286,6 +280,8 @@ pub const Inst = struct {
/// the field value expressions and optional type tag expression.
/// Uses the `pl_node` union field. Payload is `EnumDecl`.
enum_decl,
+ /// Same as `enum_decl`, except the enum is non-exhaustive.
+ enum_decl_nonexhaustive,
/// An opaque type definition. Provides an AST node only.
/// Uses the `node` union field.
opaque_decl,
@@ -332,12 +328,16 @@ pub const Inst = struct {
error_union_type,
/// `error.Foo` syntax. Uses the `str_tok` field of the Data union.
error_value,
+ /// Implements the `@export` builtin function.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ @"export",
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
/// to the named field. The field name is stored in string_bytes. Used by a.b syntax.
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
field_ptr,
/// Given a struct or object that contains virtual fields, returns the named field.
/// The field name is stored in string_bytes. Used by a.b syntax.
+ /// This instruction also accepts a pointer.
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
field_val,
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
@@ -363,11 +363,19 @@ pub const Inst = struct {
fn_type_cc,
/// Same as `fn_type_cc` but the function is variadic.
fn_type_cc_var_args,
+ /// Implements the `@hasDecl` builtin.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ has_decl,
/// `@import(operand)`.
/// Uses the `un_node` field.
import,
/// Integer literal that fits in a u64. Uses the int union value.
int,
+ /// A float literal that fits in a f32. Uses the float union value.
+ float,
+ /// A float literal that fits in a f128. Uses the `pl_node` union value.
+ /// Payload is `Float128`.
+ float128,
/// Convert an integer value to another integer type, asserting that the destination type
/// can hold the same mathematical value.
/// Uses the `pl_node` field. AST is the `@intCast` syntax.
@@ -659,11 +667,28 @@ pub const Inst = struct {
/// Given a set of `field_ptr` instructions, assumes they are all part of a struct
/// initialization expression, and emits compile errors for duplicate fields
/// as well as missing fields, if applicable.
+ /// This instruction asserts that there is at least one field_ptr instruction,
+ /// because it must use one of them to find out the struct type.
/// Uses the `pl_node` field. Payload is `Block`.
validate_struct_init_ptr,
/// A struct literal with a specified type, with no fields.
/// Uses the `un_node` field.
struct_init_empty,
+ /// Given a struct, union, enum, or opaque and a field name, returns the field type.
+ /// Uses the `pl_node` field. Payload is `FieldType`.
+ field_type,
+ /// Finalizes a typed struct initialization, performs validation, and returns the
+ /// struct value.
+ /// Uses the `pl_node` field. Payload is `StructInit`.
+ struct_init,
+ /// Converts an integer into an enum value.
+ /// Uses `pl_node` with payload `Bin`. `lhs` is enum type, `rhs` is operand.
+ int_to_enum,
+ /// Converts an enum value into an integer. Resulting type will be the tag type
+ /// of the enum. Uses `un_node`.
+ enum_to_int,
+ /// Implements the `@typeInfo` builtin. Uses `un_node`.
+ type_info,
/// Returns whether the instruction is one of the control flow "noreturn" types.
/// Function calls do not count.
@@ -709,12 +734,12 @@ pub const Inst = struct {
.cmp_gt,
.cmp_neq,
.coerce_result_ptr,
- .@"const",
.struct_decl,
.struct_decl_packed,
.struct_decl_extern,
.union_decl,
.enum_decl,
+ .enum_decl_nonexhaustive,
.opaque_decl,
.dbg_stmt_node,
.decl_ref,
@@ -727,6 +752,7 @@ pub const Inst = struct {
.elem_val_node,
.ensure_result_used,
.ensure_result_non_error,
+ .@"export",
.floatcast,
.field_ptr,
.field_val,
@@ -736,7 +762,10 @@ pub const Inst = struct {
.fn_type_var_args,
.fn_type_cc,
.fn_type_cc_var_args,
+ .has_decl,
.int,
+ .float,
+ .float128,
.intcast,
.int_type,
.is_non_null,
@@ -819,6 +848,11 @@ pub const Inst = struct {
.switch_block_ref_under_multi,
.validate_struct_init_ptr,
.struct_init_empty,
+ .struct_init,
+ .field_type,
+ .int_to_enum,
+ .enum_to_int,
+ .type_info,
=> false,
.@"break",
@@ -1181,7 +1215,6 @@ pub const Inst = struct {
}
},
bin: Bin,
- @"const": *TypedValue,
/// For strings which may contain null bytes.
str: struct {
/// Offset into `string_bytes`.
@@ -1223,6 +1256,16 @@ pub const Inst = struct {
/// Offset from Decl AST node index.
node: i32,
int: u64,
+ float: struct {
+ /// Offset from Decl AST node index.
+ /// `Tag` determines which kind of AST node this points to.
+ src_node: i32,
+ number: f32,
+
+ pub fn src(self: @This()) LazySrcLoc {
+ return .{ .node_offset = self.src_node };
+ }
+ },
array_type_sentinel: struct {
len: Ref,
/// index into extra, points to an `ArrayTypeSentinel`
@@ -1504,6 +1547,40 @@ pub const Inst = struct {
tag_type: Ref,
fields_len: u32,
};
+
+ /// A f128 value, broken up into 4 u32 parts.
+ pub const Float128 = struct {
+ piece0: u32,
+ piece1: u32,
+ piece2: u32,
+ piece3: u32,
+
+ pub fn get(self: Float128) f128 {
+ const int_bits = @as(u128, self.piece0) |
+ (@as(u128, self.piece1) << 32) |
+ (@as(u128, self.piece2) << 64) |
+ (@as(u128, self.piece3) << 96);
+ return @bitCast(f128, int_bits);
+ }
+ };
+
+ /// Trailing is an item per field.
+ pub const StructInit = struct {
+ fields_len: u32,
+
+ pub const Item = struct {
+ /// The `field_type` ZIR instruction for this field init.
+ field_type: Index,
+ /// The field init expression to be used as the field value.
+ init: Ref,
+ };
+ };
+
+ pub const FieldType = struct {
+ container_type: Ref,
+ /// Offset into `string_bytes`, null terminated.
+ name_start: u32,
+ };
};
pub const SpecialProng = enum { none, @"else", under };
@@ -1533,12 +1610,11 @@ const Writer = struct {
.intcast,
.store,
.store_to_block_ptr,
+ .store_to_inferred_ptr,
=> try self.writeBin(stream, inst),
.alloc,
.alloc_mut,
- .alloc_inferred,
- .alloc_inferred_mut,
.indexable_ptr_len,
.bit_not,
.bool_not,
@@ -1578,6 +1654,8 @@ const Writer = struct {
.typeof,
.typeof_elem,
.struct_init_empty,
+ .enum_to_int,
+ .type_info,
=> try self.writeUnNode(stream, inst),
.ref,
@@ -1591,11 +1669,12 @@ const Writer = struct {
=> try self.writeBoolBr(stream, inst),
.array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst),
- .@"const" => try self.writeConst(stream, inst),
.param_type => try self.writeParamType(stream, inst),
.ptr_type_simple => try self.writePtrTypeSimple(stream, inst),
.ptr_type => try self.writePtrType(stream, inst),
.int => try self.writeInt(stream, inst),
+ .float => try self.writeFloat(stream, inst),
+ .float128 => try self.writeFloat128(stream, inst),
.str => try self.writeStr(stream, inst),
.elided => try stream.writeAll(")"),
.int_type => try self.writeIntType(stream, inst),
@@ -1616,6 +1695,9 @@ const Writer = struct {
.slice_sentinel,
.union_decl,
.enum_decl,
+ .enum_decl_nonexhaustive,
+ .struct_init,
+ .field_type,
=> try self.writePlNode(stream, inst),
.add,
@@ -1635,15 +1717,18 @@ const Writer = struct {
.cmp_gt,
.cmp_neq,
.div,
+ .has_decl,
.mod_rem,
.shl,
.shr,
.xor,
.store_node,
.error_union_type,
+ .@"export",
.merge_error_sets,
.bit_and,
.bit_or,
+ .int_to_enum,
=> try self.writePlNodeBin(stream, inst),
.call,
@@ -1701,6 +1786,8 @@ const Writer = struct {
.ret_type,
.repeat,
.repeat_inline,
+ .alloc_inferred,
+ .alloc_inferred_mut,
=> try self.writeNode(stream, inst),
.error_value,
@@ -1726,7 +1813,6 @@ const Writer = struct {
.bitcast,
.bitcast_result_ptr,
- .store_to_inferred_ptr,
=> try stream.writeAll("TODO)"),
}
}
@@ -1770,15 +1856,6 @@ const Writer = struct {
try stream.writeAll("TODO)");
}
- fn writeConst(
- self: *Writer,
- stream: anytype,
- inst: Inst.Index,
- ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
- const inst_data = self.code.instructions.items(.data)[inst].@"const";
- try stream.writeAll("TODO)");
- }
-
fn writeParamType(
self: *Writer,
stream: anytype,
@@ -1816,6 +1893,23 @@ const Writer = struct {
try stream.print("{d})", .{inst_data});
}
+ fn writeFloat(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].float;
+ const src = inst_data.src();
+ try stream.print("{d}) ", .{inst_data.number});
+ try self.writeSrc(stream, src);
+ }
+
+ fn writeFloat128(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.Float128, inst_data.payload_index).data;
+ const src = inst_data.src();
+ const number = extra.get();
+ // TODO improve std.format to be able to print f128 values
+ try stream.print("{d}) ", .{@floatCast(f64, number)});
+ try self.writeSrc(stream, src);
+ }
+
fn writeStr(
self: *Writer,
stream: anytype,
@@ -2133,7 +2227,8 @@ const Writer = struct {
fn writePlNodeDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
- const decl = self.code.decls[inst_data.payload_index];
+ const owner_decl = self.scope.ownerDecl().?;
+ const decl = owner_decl.dependencies.entries.items[inst_data.payload_index].key;
try stream.print("{s}) ", .{decl.name});
try self.writeSrc(stream, inst_data.src());
}
diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig
@@ -1308,4 +1308,106 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
\\ ufoo = (uval += 100000000); // compile error if @truncate() not inserted
\\}
, "");
+
+ cases.add("basic vector expressions",
+ \\#include <stdlib.h>
+ \\#include <stdint.h>
+ \\typedef int16_t __v8hi __attribute__((__vector_size__(16)));
+ \\int main(int argc, char**argv) {
+ \\ __v8hi uninitialized;
+ \\ __v8hi empty_init = {};
+ \\ __v8hi partial_init = {0, 1, 2, 3};
+ \\
+ \\ __v8hi a = {0, 1, 2, 3, 4, 5, 6, 7};
+ \\ __v8hi b = (__v8hi) {100, 200, 300, 400, 500, 600, 700, 800};
+ \\
+ \\ __v8hi sum = a + b;
+ \\ for (int i = 0; i < 8; i++) {
+ \\ if (sum[i] != a[i] + b[i]) abort();
+ \\ }
+ \\ return 0;
+ \\}
+ , "");
+
+ cases.add("__builtin_shufflevector",
+ \\#include <stdlib.h>
+ \\#include <stdint.h>
+ \\typedef int16_t __v4hi __attribute__((__vector_size__(8)));
+ \\typedef int16_t __v8hi __attribute__((__vector_size__(16)));
+ \\int main(int argc, char**argv) {
+ \\ __v8hi v8_a = {0, 1, 2, 3, 4, 5, 6, 7};
+ \\ __v8hi v8_b = {100, 200, 300, 400, 500, 600, 700, 800};
+ \\ __v8hi shuffled = __builtin_shufflevector(v8_a, v8_b, 0, 1, 2, 3, 8, 9, 10, 11);
+ \\ for (int i = 0; i < 8; i++) {
+ \\ if (i < 4) {
+ \\ if (shuffled[i] != v8_a[i]) abort();
+ \\ } else {
+ \\ if (shuffled[i] != v8_b[i - 4]) abort();
+ \\ }
+ \\ }
+ \\ shuffled = __builtin_shufflevector(
+ \\ (__v8hi) {-1, -1, -1, -1, -1, -1, -1, -1},
+ \\ (__v8hi) {42, 42, 42, 42, 42, 42, 42, 42},
+ \\ 0, 1, 2, 3, 8, 9, 10, 11
+ \\ );
+ \\ for (int i = 0; i < 8; i++) {
+ \\ if (i < 4) {
+ \\ if (shuffled[i] != -1) abort();
+ \\ } else {
+ \\ if (shuffled[i] != 42) abort();
+ \\ }
+ \\ }
+ \\ __v4hi shuffled_to_fewer_elements = __builtin_shufflevector(v8_a, v8_b, 0, 1, 8, 9);
+ \\ for (int i = 0; i < 4; i++) {
+ \\ if (i < 2) {
+ \\ if (shuffled_to_fewer_elements[i] != v8_a[i]) abort();
+ \\ } else {
+ \\ if (shuffled_to_fewer_elements[i] != v8_b[i - 2]) abort();
+ \\ }
+ \\ }
+ \\ __v4hi v4_a = {0, 1, 2, 3};
+ \\ __v4hi v4_b = {100, 200, 300, 400};
+ \\ __v8hi shuffled_to_more_elements = __builtin_shufflevector(v4_a, v4_b, 0, 1, 2, 3, 4, 5, 6, 7);
+ \\ for (int i = 0; i < 4; i++) {
+ \\ if (shuffled_to_more_elements[i] != v4_a[i]) abort();
+ \\ if (shuffled_to_more_elements[i + 4] != v4_b[i]) abort();
+ \\ }
+ \\ return 0;
+ \\}
+ , "");
+
+ cases.add("__builtin_convertvector",
+ \\#include <stdlib.h>
+ \\#include <stdint.h>
+ \\typedef int16_t __v8hi __attribute__((__vector_size__(16)));
+ \\typedef uint16_t __v8hu __attribute__((__vector_size__(16)));
+ \\int main(int argc, char**argv) {
+ \\ __v8hi signed_vector = { 1, 2, 3, 4, -1, -2, -3,-4};
+ \\ __v8hu unsigned_vector = __builtin_convertvector(signed_vector, __v8hu);
+ \\
+ \\ for (int i = 0; i < 8; i++) {
+ \\ if (unsigned_vector[i] != (uint16_t)signed_vector[i]) abort();
+ \\ }
+ \\ return 0;
+ \\}
+ , "");
+
+ cases.add("vector casting",
+ \\#include <stdlib.h>
+ \\#include <stdint.h>
+ \\typedef int8_t __v8qi __attribute__((__vector_size__(8)));
+ \\typedef uint8_t __v8qu __attribute__((__vector_size__(8)));
+ \\int main(int argc, char**argv) {
+ \\ __v8qi signed_vector = { 1, 2, 3, 4, -1, -2, -3,-4};
+ \\
+ \\ uint64_t big_int = (uint64_t) signed_vector;
+ \\ if (big_int != 0x01020304FFFEFDFCULL && big_int != 0xFCFDFEFF04030201ULL) abort();
+ \\ __v8qu unsigned_vector = (__v8qu) big_int;
+ \\ for (int i = 0; i < 8; i++) {
+ \\ if (unsigned_vector[i] != (uint8_t)signed_vector[i] && unsigned_vector[i] != (uint8_t)signed_vector[7 - i]) abort();
+ \\ }
+ \\ return 0;
+ \\}
+ , "");
+
}
diff --git a/test/stack_traces.zig b/test/stack_traces.zig
@@ -3,16 +3,56 @@ const os = std.os;
const tests = @import("tests.zig");
pub fn addCases(cases: *tests.StackTracesContext) void {
- const source_return =
- \\const std = @import("std");
- \\
+ cases.addCase(.{
+ .name = "return",
+ .source =
\\pub fn main() !void {
\\ return error.TheSkyIsFalling;
\\}
- ;
- const source_try_return =
- \\const std = @import("std");
- \\
+ ,
+ .Debug = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\source.zig:2:5: [address] in main (test)
+ \\ return error.TheSkyIsFalling;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseSafe = .{
+ .exclude = struct {
+ pub fn exclude() bool {
+ return if (std.builtin.object_format == .elf) true else false;
+ }
+ },
+ .exclude_os = .{
+ .windows, // segfault
+ },
+ .expect =
+ \\error: TheSkyIsFalling
+ \\source.zig:2:5: [address] in [function]
+ \\ return error.TheSkyIsFalling;
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseFast = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\
+ ,
+ },
+ .ReleaseSmall = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\
+ ,
+ },
+ });
+
+ cases.addCase(.{
+ .name = "try return",
+ .source =
\\fn foo() !void {
\\ return error.TheSkyIsFalling;
\\}
@@ -20,10 +60,56 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\pub fn main() !void {
\\ try foo();
\\}
- ;
- const source_try_try_return_return =
- \\const std = @import("std");
- \\
+ ,
+ .Debug = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\source.zig:2:5: [address] in foo (test)
+ \\ return error.TheSkyIsFalling;
+ \\ ^
+ \\source.zig:6:5: [address] in main (test)
+ \\ try foo();
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseSafe = .{
+ .exclude = struct {
+ pub fn exclude() bool {
+ return if (std.builtin.object_format == .elf) true else false;
+ }
+ },
+ .exclude_os = .{
+ .windows, // segfault
+ },
+ .expect =
+ \\error: TheSkyIsFalling
+ \\source.zig:2:5: [address] in [function]
+ \\ return error.TheSkyIsFalling;
+ \\ ^
+ \\source.zig:6:5: [address] in [function]
+ \\ try foo();
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseFast = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\
+ ,
+ },
+ .ReleaseSmall = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\
+ ,
+ },
+ });
+
+ cases.addCase(.{
+ .name = "try try return return",
+ .source =
\\fn foo() !void {
\\ try bar();
\\}
@@ -39,9 +125,71 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\pub fn main() !void {
\\ try foo();
\\}
- ;
+ ,
+ .Debug = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\source.zig:10:5: [address] in make_error (test)
+ \\ return error.TheSkyIsFalling;
+ \\ ^
+ \\source.zig:6:5: [address] in bar (test)
+ \\ return make_error();
+ \\ ^
+ \\source.zig:2:5: [address] in foo (test)
+ \\ try bar();
+ \\ ^
+ \\source.zig:14:5: [address] in main (test)
+ \\ try foo();
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseSafe = .{
+ .exclude = struct {
+ pub fn exclude() bool {
+ return if (std.builtin.object_format == .elf) true else false;
+ }
+ },
+ .exclude_os = .{
+ .windows, // segfault
+ },
+ .expect =
+ \\error: TheSkyIsFalling
+ \\source.zig:10:5: [address] in [function]
+ \\ return error.TheSkyIsFalling;
+ \\ ^
+ \\source.zig:6:5: [address] in [function]
+ \\ return make_error();
+ \\ ^
+ \\source.zig:2:5: [address] in [function]
+ \\ try bar();
+ \\ ^
+ \\source.zig:14:5: [address] in [function]
+ \\ try foo();
+ \\ ^
+ \\
+ ,
+ },
+ .ReleaseFast = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\
+ ,
+ },
+ .ReleaseSmall = .{
+ .expect =
+ \\error: TheSkyIsFalling
+ \\
+ ,
+ },
+ });
- const source_dumpCurrentStackTrace =
+ cases.addCase(.{
+ .exclude_os = .{
+ .windows,
+ },
+ .name = "dumpCurrentStackTrace",
+ .source =
\\const std = @import("std");
\\
\\fn bar() void {
@@ -54,401 +202,17 @@ pub fn addCases(cases: *tests.StackTracesContext) void {
\\ foo();
\\ return 1;
\\}
- ;
-
- switch (std.Target.current.os.tag) {
- .freebsd => {
- cases.addCase(
- "return",
- source_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in main (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\
- ,
- // release-safe
- // https://github.com/ziglang/zig/issues/8421
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try return",
- source_try_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in foo (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- // https://github.com/ziglang/zig/issues/8421
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try try return return",
- source_try_try_return_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:12:5: [address] in make_error (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in bar (test)
- \\ return make_error();
- \\ ^
- \\source.zig:4:5: [address] in foo (test)
- \\ try bar();
- \\ ^
- \\source.zig:16:5: [address] in main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- // https://github.com/ziglang/zig/issues/8421
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- },
- .linux => {
- cases.addCase(
- "return",
- source_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in main (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\
- ,
- // release-safe
- // https://github.com/ziglang/zig/issues/8421
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try return",
- source_try_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in foo (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- // https://github.com/ziglang/zig/issues/8421
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try try return return",
- source_try_try_return_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:12:5: [address] in make_error (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in bar (test)
- \\ return make_error();
- \\ ^
- \\source.zig:4:5: [address] in foo (test)
- \\ try bar();
- \\ ^
- \\source.zig:16:5: [address] in main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- // https://github.com/ziglang/zig/issues/8421
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "dumpCurrentStackTrace",
- source_dumpCurrentStackTrace,
- [_][]const u8{
- // debug
- \\source.zig:7:8: [address] in foo (test)
- \\ bar();
- \\ ^
- \\source.zig:10:8: [address] in main (test)
- \\ foo();
- \\ ^
- \\start.zig:342:29: [address] in std.start.posixCallMainAndExit (test)
- \\ return root.main();
- \\ ^
- \\start.zig:163:5: [address] in std.start._start (test)
- \\ @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
- \\ ^
- \\
- ,
- // release-safe
- // https://github.com/ziglang/zig/issues/8421
- \\
- ,
- // release-fast
- \\
- ,
- // release-small
- \\
- },
- );
- },
- .macos => {
- cases.addCase(
- "return",
- source_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in main (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\
- ,
- // release-safe
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in std.start.main (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try return",
- source_try_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in foo (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in std.start.main (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in std.start.main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try try return return",
- source_try_try_return_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:12:5: [address] in make_error (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in bar (test)
- \\ return make_error();
- \\ ^
- \\source.zig:4:5: [address] in foo (test)
- \\ try bar();
- \\ ^
- \\source.zig:16:5: [address] in main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- \\error: TheSkyIsFalling
- \\source.zig:12:5: [address] in std.start.main (test)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in std.start.main (test)
- \\ return make_error();
- \\ ^
- \\source.zig:4:5: [address] in std.start.main (test)
- \\ try bar();
- \\ ^
- \\source.zig:16:5: [address] in std.start.main (test)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- },
- .windows => {
- cases.addCase(
- "return",
- source_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in main (test.obj)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\
- ,
- // release-safe
- // --disabled-- results in segmenetation fault
- "",
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try return",
- source_try_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:4:5: [address] in foo (test.obj)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in main (test.obj)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- // --disabled-- results in segmenetation fault
- "",
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
- cases.addCase(
- "try try return return",
- source_try_try_return_return,
- [_][]const u8{
- // debug
- \\error: TheSkyIsFalling
- \\source.zig:12:5: [address] in make_error (test.obj)
- \\ return error.TheSkyIsFalling;
- \\ ^
- \\source.zig:8:5: [address] in bar (test.obj)
- \\ return make_error();
- \\ ^
- \\source.zig:4:5: [address] in foo (test.obj)
- \\ try bar();
- \\ ^
- \\source.zig:16:5: [address] in main (test.obj)
- \\ try foo();
- \\ ^
- \\
- ,
- // release-safe
- // --disabled-- results in segmenetation fault
- "",
- // release-fast
- \\error: TheSkyIsFalling
- \\
- ,
- // release-small
- \\error: TheSkyIsFalling
- \\
- },
- );
+ ,
+ .Debug = .{
+ .expect =
+ \\source.zig:7:8: [address] in foo (test)
+ \\ bar();
+ \\ ^
+ \\source.zig:10:8: [address] in main (test)
+ \\ foo();
+ \\ ^
+ \\
+ ,
},
- else => {},
- }
+ });
}
diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig
@@ -280,6 +280,15 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "");
+ // If expression with breakpoint that does not get hit
+ case.addCompareOutput(
+ \\export fn main() c_int {
+ \\ var x: i32 = 1;
+ \\ if (x != 1) @breakpoint();
+ \\ return 0;
+ \\}
+ , "");
+
// Switch expression
case.addCompareOutput(
\\export fn main() c_int {
@@ -481,6 +490,310 @@ pub fn addCases(ctx: *TestContext) !void {
\\}
, "");
}
+
+ {
+ var case = ctx.exeFromCompiledC("structs", .{});
+ case.addError(
+ \\const Point = struct { x: i32, y: i32 };
+ \\export fn main() c_int {
+ \\ var p: Point = .{
+ \\ .y = 24,
+ \\ .x = 12,
+ \\ .y = 24,
+ \\ };
+ \\ return p.y - p.x - p.x;
+ \\}
+ , &.{
+ ":6:10: error: duplicate field",
+ ":4:10: note: other field here",
+ });
+ case.addError(
+ \\const Point = struct { x: i32, y: i32 };
+ \\export fn main() c_int {
+ \\ var p: Point = .{
+ \\ .y = 24,
+ \\ };
+ \\ return p.y - p.x - p.x;
+ \\}
+ , &.{
+ ":3:21: error: mising struct field: x",
+ ":1:15: note: struct 'Point' declared here",
+ });
+ case.addError(
+ \\const Point = struct { x: i32, y: i32 };
+ \\export fn main() c_int {
+ \\ var p: Point = .{
+ \\ .x = 12,
+ \\ .y = 24,
+ \\ .z = 48,
+ \\ };
+ \\ return p.y - p.x - p.x;
+ \\}
+ , &.{
+ ":6:10: error: no field named 'z' in struct 'Point'",
+ ":1:15: note: struct declared here",
+ });
+ case.addCompareOutput(
+ \\const Point = struct { x: i32, y: i32 };
+ \\export fn main() c_int {
+ \\ var p: Point = .{
+ \\ .x = 12,
+ \\ .y = 24,
+ \\ };
+ \\ return p.y - p.x - p.x;
+ \\}
+ , "");
+ }
+
+ {
+ var case = ctx.exeFromCompiledC("enums", .{});
+
+ case.addError(
+ \\const E1 = packed enum { a, b, c };
+ \\const E2 = extern enum { a, b, c };
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ \\export fn bar() void {
+ \\ const x = E2.a;
+ \\}
+ , &.{
+ ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
+ ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
+ });
+
+ // comptime and types are caught in AstGen.
+ case.addError(
+ \\const E1 = enum {
+ \\ a,
+ \\ comptime b,
+ \\ c,
+ \\};
+ \\const E2 = enum {
+ \\ a,
+ \\ b: i32,
+ \\ c,
+ \\};
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ \\export fn bar() void {
+ \\ const x = E2.a;
+ \\}
+ , &.{
+ ":3:5: error: enum fields cannot be marked comptime",
+ ":8:8: error: enum fields do not have types",
+ });
+
+ // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch
+ case.addCompareOutput(
+ \\const Number = enum { One, Two, Three };
+ \\
+ \\export fn main() c_int {
+ \\ var number1 = Number.One;
+ \\ var number2: Number = .Two;
+ \\ const number3 = @intToEnum(Number, 2);
+ \\ if (number1 == number2) return 1;
+ \\ if (number2 == number3) return 1;
+ \\ if (@enumToInt(number1) != 0) return 1;
+ \\ if (@enumToInt(number2) != 1) return 1;
+ \\ if (@enumToInt(number3) != 2) return 1;
+ \\ var x: Number = .Two;
+ \\ if (number2 != x) return 1;
+ \\ switch (x) {
+ \\ .One => return 1,
+ \\ .Two => return 0,
+ \\ number3 => return 2,
+ \\ }
+ \\}
+ , "");
+
+ // Specifying alignment is a parse error.
+ // This also tests going from a successful build to a parse error.
+ case.addError(
+ \\const E1 = enum {
+ \\ a,
+ \\ b align(4),
+ \\ c,
+ \\};
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ , &.{
+ ":3:7: error: expected ',', found 'align'",
+ });
+
+ // Redundant non-exhaustive enum mark.
+ // This also tests going from a parse error to an AstGen error.
+ case.addError(
+ \\const E1 = enum {
+ \\ a,
+ \\ _,
+ \\ b,
+ \\ c,
+ \\ _,
+ \\};
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ , &.{
+ ":6:5: error: redundant non-exhaustive enum mark",
+ ":3:5: note: other mark here",
+ });
+
+ case.addError(
+ \\const E1 = enum {
+ \\ a,
+ \\ b,
+ \\ c,
+ \\ _ = 10,
+ \\};
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ , &.{
+ ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
+ });
+
+ case.addError(
+ \\const E1 = enum {};
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ , &.{
+ ":1:12: error: enum declarations must have at least one tag",
+ });
+
+ case.addError(
+ \\const E1 = enum { a, b, _ };
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ , &.{
+ ":1:12: error: non-exhaustive enum missing integer tag type",
+ ":1:25: note: marked non-exhaustive here",
+ });
+
+ case.addError(
+ \\const E1 = enum { a, b, c, b, d };
+ \\export fn foo() void {
+ \\ const x = E1.a;
+ \\}
+ , &.{
+ ":1:28: error: duplicate enum tag",
+ ":1:22: note: other tag here",
+ });
+
+ case.addError(
+ \\export fn foo() void {
+ \\ const a = true;
+ \\ const b = @enumToInt(a);
+ \\}
+ , &.{
+ ":3:26: error: expected enum or tagged union, found bool",
+ });
+
+ case.addError(
+ \\export fn foo() void {
+ \\ const a = 1;
+ \\ const b = @intToEnum(bool, a);
+ \\}
+ , &.{
+ ":3:26: error: expected enum, found bool",
+ });
+
+ case.addError(
+ \\const E = enum { a, b, c };
+ \\export fn foo() void {
+ \\ const b = @intToEnum(E, 3);
+ \\}
+ , &.{
+ ":3:15: error: enum 'E' has no tag with value 3",
+ ":1:11: note: enum declared here",
+ });
+
+ case.addError(
+ \\const E = enum { a, b, c };
+ \\export fn foo() void {
+ \\ var x: E = .a;
+ \\ switch (x) {
+ \\ .a => {},
+ \\ .c => {},
+ \\ }
+ \\}
+ , &.{
+ ":4:5: error: switch must handle all possibilities",
+ ":4:5: note: unhandled enumeration value: 'b'",
+ ":1:11: note: enum 'E' declared here",
+ });
+
+ case.addError(
+ \\const E = enum { a, b, c };
+ \\export fn foo() void {
+ \\ var x: E = .a;
+ \\ switch (x) {
+ \\ .a => {},
+ \\ .b => {},
+ \\ .b => {},
+ \\ .c => {},
+ \\ }
+ \\}
+ , &.{
+ ":7:10: error: duplicate switch value",
+ ":6:10: note: previous value here",
+ });
+
+ case.addError(
+ \\const E = enum { a, b, c };
+ \\export fn foo() void {
+ \\ var x: E = .a;
+ \\ switch (x) {
+ \\ .a => {},
+ \\ .b => {},
+ \\ .c => {},
+ \\ else => {},
+ \\ }
+ \\}
+ , &.{
+ ":8:14: error: unreachable else prong; all cases already handled",
+ });
+
+ case.addError(
+ \\const E = enum { a, b, c };
+ \\export fn foo() void {
+ \\ var x: E = .a;
+ \\ switch (x) {
+ \\ .a => {},
+ \\ .b => {},
+ \\ _ => {},
+ \\ }
+ \\}
+ , &.{
+ ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums",
+ ":7:11: note: '_' prong here",
+ });
+
+ case.addError(
+ \\const E = enum { a, b, c };
+ \\export fn foo() void {
+ \\ var x = E.d;
+ \\}
+ , &.{
+ ":3:14: error: enum 'E' has no member named 'd'",
+ ":1:11: note: enum declared here",
+ });
+
+ case.addError(
+ \\const E = enum { a, b, c };
+ \\export fn foo() void {
+ \\ var x: E = .d;
+ \\}
+ , &.{
+ ":3:17: error: enum 'E' has no field named 'd'",
+ ":1:11: note: enum declared here",
+ });
+ }
+
ctx.c("empty start function", linux_x64,
\\export fn _start() noreturn {
\\ unreachable;
@@ -489,8 +802,8 @@ pub fn addCases(ctx: *TestContext) !void {
\\ZIG_EXTERN_C zig_noreturn void _start(void);
\\
\\zig_noreturn void _start(void) {
- \\ zig_breakpoint();
- \\ zig_unreachable();
+ \\ zig_breakpoint();
+ \\ zig_unreachable();
\\}
\\
);
diff --git a/test/stage2/test.zig b/test/stage2/test.zig
@@ -941,6 +941,32 @@ pub fn addCases(ctx: *TestContext) !void {
"",
);
+ // Array access to a global array.
+ case.addCompareOutput(
+ \\const hello = "hello".*;
+ \\export fn _start() noreturn {
+ \\ assert(hello[1] == 'e');
+ \\
+ \\ exit();
+ \\}
+ \\
+ \\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;
+ \\}
+ ,
+ "",
+ );
+
// 64bit set stack
case.addCompareOutput(
\\export fn _start() noreturn {
@@ -1022,7 +1048,7 @@ pub fn addCases(ctx: *TestContext) !void {
"Hello, World!\n",
);
try case.files.append(.{
- .src =
+ .src =
\\pub fn print() void {
\\ asm volatile ("syscall"
\\ :
@@ -1038,11 +1064,61 @@ pub fn addCases(ctx: *TestContext) !void {
.path = "print.zig",
});
}
+ {
+ var case = ctx.exe("import private", linux_x64);
+ case.addError(
+ \\export fn _start() noreturn {
+ \\ @import("print.zig").print();
+ \\ exit();
+ \\}
+ \\
+ \\fn exit() noreturn {
+ \\ asm volatile ("syscall"
+ \\ :
+ \\ : [number] "{rax}" (231),
+ \\ [arg1] "{rdi}" (@as(usize, 0))
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ unreachable;
+ \\}
+ ,
+ &.{":2:25: error: 'print' is private"},
+ );
+ try case.files.append(.{
+ .src =
+ \\fn print() void {
+ \\ asm volatile ("syscall"
+ \\ :
+ \\ : [number] "{rax}" (@as(usize, 1)),
+ \\ [arg1] "{rdi}" (@as(usize, 1)),
+ \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")),
+ \\ [arg3] "{rdx}" (@as(usize, 14))
+ \\ : "rcx", "r11", "memory"
+ \\ );
+ \\ return;
+ \\}
+ ,
+ .path = "print.zig",
+ });
+ }
ctx.compileError("function redefinition", linux_x64,
+ \\// dummy comment
\\fn entry() void {}
\\fn entry() void {}
- , &[_][]const u8{":2:4: error: redefinition of 'entry'"});
+ , &[_][]const u8{
+ ":3:4: error: redefinition of 'entry'",
+ ":2:1: note: previous definition here",
+ });
+
+ ctx.compileError("global variable redefinition", linux_x64,
+ \\// dummy comment
+ \\var foo = false;
+ \\var foo = true;
+ , &[_][]const u8{
+ ":3:5: error: redefinition of 'foo'",
+ ":2:1: note: previous definition here",
+ });
ctx.compileError("compileError", linux_x64,
\\export fn _start() noreturn {
diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig
@@ -121,6 +121,138 @@ pub fn addCases(ctx: *TestContext) !void {
\\ return x + y;
\\}
, "35\n");
+
+ case.addCompareOutput(
+ \\export fn _start() u32 {
+ \\ var i: u32 = 20;
+ \\ i -= 5;
+ \\ return i;
+ \\}
+ , "15\n");
+
+ case.addCompareOutput(
+ \\export fn _start() u32 {
+ \\ var i: u32 = 5;
+ \\ i -= 3;
+ \\ var result: u32 = foo(i, 10);
+ \\ return result;
+ \\}
+ \\fn foo(x: u32, y: u32) u32 {
+ \\ return y - x;
+ \\}
+ , "8\n");
+
+ case.addCompareOutput(
+ \\export fn _start() u32 {
+ \\ var i: u32 = 5;
+ \\ i *= 7;
+ \\ var result: u32 = foo(i, 10);
+ \\ return result;
+ \\}
+ \\fn foo(x: u32, y: u32) u32 {
+ \\ return x * y;
+ \\}
+ , "350\n");
+
+ case.addCompareOutput(
+ \\export fn _start() u32 {
+ \\ var i: u32 = 352;
+ \\ i /= 7; // i = 50
+ \\ var result: u32 = foo(i, 7);
+ \\ return result;
+ \\}
+ \\fn foo(x: u32, y: u32) u32 {
+ \\ return x / y;
+ \\}
+ , "7\n");
+
+ case.addCompareOutput(
+ \\export fn _start() u32 {
+ \\ var i: u32 = 5;
+ \\ i &= 6;
+ \\ return i;
+ \\}
+ , "4\n");
+
+ case.addCompareOutput(
+ \\export fn _start() u32 {
+ \\ var i: u32 = 5;
+ \\ i |= 6;
+ \\ return i;
+ \\}
+ , "7\n");
+
+ case.addCompareOutput(
+ \\export fn _start() u32 {
+ \\ var i: u32 = 5;
+ \\ i ^= 6;
+ \\ return i;
+ \\}
+ , "3\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = false;
+ \\ b = b or false;
+ \\ return b;
+ \\}
+ , "0\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = true;
+ \\ b = b or false;
+ \\ return b;
+ \\}
+ , "1\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = false;
+ \\ b = b or true;
+ \\ return b;
+ \\}
+ , "1\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = true;
+ \\ b = b or true;
+ \\ return b;
+ \\}
+ , "1\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = false;
+ \\ b = b and false;
+ \\ return b;
+ \\}
+ , "0\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = true;
+ \\ b = b and false;
+ \\ return b;
+ \\}
+ , "0\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = false;
+ \\ b = b and true;
+ \\ return b;
+ \\}
+ , "0\n");
+
+ case.addCompareOutput(
+ \\export fn _start() bool {
+ \\ var b: bool = true;
+ \\ b = b and true;
+ \\ return b;
+ \\}
+ , "1\n");
}
{
diff --git a/test/tests.zig b/test/tests.zig
@@ -566,42 +566,87 @@ pub const StackTracesContext = struct {
const Expect = [@typeInfo(Mode).Enum.fields.len][]const u8;
- pub fn addCase(
+ pub fn addCase(self: *StackTracesContext, config: anytype) void {
+ if (@hasField(@TypeOf(config), "exclude")) {
+ if (config.exclude.exclude()) return;
+ }
+ if (@hasField(@TypeOf(config), "exclude_arch")) {
+ const exclude_arch: []const builtin.Cpu.Arch = &config.exclude_arch;
+ for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return;
+ }
+ if (@hasField(@TypeOf(config), "exclude_os")) {
+ const exclude_os: []const builtin.Os.Tag = &config.exclude_os;
+ for (exclude_os) |os| if (os == builtin.os.tag) return;
+ }
+ for (self.modes) |mode| {
+ switch (mode) {
+ .Debug => {
+ if (@hasField(@TypeOf(config), "Debug")) {
+ self.addExpect(config.name, config.source, mode, config.Debug);
+ }
+ },
+ .ReleaseSafe => {
+ if (@hasField(@TypeOf(config), "ReleaseSafe")) {
+ self.addExpect(config.name, config.source, mode, config.ReleaseSafe);
+ }
+ },
+ .ReleaseFast => {
+ if (@hasField(@TypeOf(config), "ReleaseFast")) {
+ self.addExpect(config.name, config.source, mode, config.ReleaseFast);
+ }
+ },
+ .ReleaseSmall => {
+ if (@hasField(@TypeOf(config), "ReleaseSmall")) {
+ self.addExpect(config.name, config.source, mode, config.ReleaseSmall);
+ }
+ },
+ }
+ }
+ }
+
+ fn addExpect(
self: *StackTracesContext,
name: []const u8,
source: []const u8,
- expect: Expect,
+ mode: Mode,
+ mode_config: anytype,
) void {
- const b = self.b;
-
- for (self.modes) |mode| {
- const expect_for_mode = expect[@enumToInt(mode)];
- if (expect_for_mode.len == 0) continue;
-
- const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{
- "stack-trace",
- name,
- @tagName(mode),
- }) catch unreachable;
- if (self.test_filter) |filter| {
- if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
- }
-
- const src_basename = "source.zig";
- const write_src = b.addWriteFile(src_basename, source);
- const exe = b.addExecutableFromWriteFileStep("test", write_src, src_basename);
- exe.setBuildMode(mode);
-
- const run_and_compare = RunAndCompareStep.create(
- self,
- exe,
- annotated_case_name,
- mode,
- expect_for_mode,
- );
+ if (@hasField(@TypeOf(mode_config), "exclude")) {
+ if (mode_config.exclude.exclude()) return;
+ }
+ if (@hasField(@TypeOf(mode_config), "exclude_arch")) {
+ const exclude_arch: []const builtin.Cpu.Arch = &mode_config.exclude_arch;
+ for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return;
+ }
+ if (@hasField(@TypeOf(mode_config), "exclude_os")) {
+ const exclude_os: []const builtin.Os.Tag = &mode_config.exclude_os;
+ for (exclude_os) |os| if (os == builtin.os.tag) return;
+ }
- self.step.dependOn(&run_and_compare.step);
+ const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{
+ "stack-trace",
+ name,
+ @tagName(mode),
+ }) catch unreachable;
+ if (self.test_filter) |filter| {
+ if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
}
+
+ const b = self.b;
+ const src_basename = "source.zig";
+ const write_src = b.addWriteFile(src_basename, source);
+ const exe = b.addExecutableFromWriteFileStep("test", write_src, src_basename);
+ exe.setBuildMode(mode);
+
+ const run_and_compare = RunAndCompareStep.create(
+ self,
+ exe,
+ annotated_case_name,
+ mode,
+ mode_config.expect,
+ );
+
+ self.step.dependOn(&run_and_compare.step);
}
const RunAndCompareStep = struct {
@@ -703,6 +748,7 @@ pub const StackTracesContext = struct {
// process result
// - keep only basename of source file path
// - replace address with symbolic string
+ // - replace function name with symbolic string when mode != .Debug
// - skip empty lines
const got: []const u8 = got_result: {
var buf = ArrayList(u8).init(b.allocator);
@@ -711,26 +757,45 @@ pub const StackTracesContext = struct {
var it = mem.split(stderr, "\n");
process_lines: while (it.next()) |line| {
if (line.len == 0) continue;
- const delims = [_][]const u8{ ":", ":", ":", " in " };
- var marks = [_]usize{0} ** 4;
// offset search past `[drive]:` on windows
var pos: usize = if (std.Target.current.os.tag == .windows) 2 else 0;
+ // locate delims/anchor
+ const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" };
+ var marks = [_]usize{0} ** delims.len;
for (delims) |delim, i| {
marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse {
+ // unexpected pattern: emit raw line and cont
try buf.appendSlice(line);
try buf.appendSlice("\n");
continue :process_lines;
};
pos = marks[i] + delim.len;
}
+ // locate source basename
pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse {
+ // unexpected pattern: emit raw line and cont
try buf.appendSlice(line);
try buf.appendSlice("\n");
continue :process_lines;
};
+ // end processing if source basename changes
+ if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break;
+ // emit substituted line
try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]);
try buf.appendSlice(" [address]");
- try buf.appendSlice(line[marks[3]..]);
+ if (self.mode == .Debug) {
+ if (mem.lastIndexOfScalar(u8, line[marks[4]..marks[5]], '.')) |idot| {
+ // On certain platforms (windows) or possibly depending on how we choose to link main
+ // the object file extension may be present so we simply strip any extension.
+ try buf.appendSlice(line[marks[3] .. marks[4] + idot]);
+ try buf.appendSlice(line[marks[5]..]);
+ } else {
+ try buf.appendSlice(line[marks[3]..]);
+ }
+ } else {
+ try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]);
+ try buf.appendSlice("[function]");
+ }
try buf.appendSlice("\n");
}
break :got_result buf.toOwnedSlice();
diff --git a/test/translate_c.zig b/test/translate_c.zig
@@ -2529,6 +2529,14 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\}
});
+ cases.add("macro call with no args",
+ \\#define CALL(arg) bar()
+ , &[_][]const u8{
+ \\pub fn CALL(arg: anytype) callconv(.Inline) @TypeOf(bar()) {
+ \\ return bar();
+ \\}
+ });
+
cases.add("logical and, logical or",
\\int max(int a, int b) {
\\ if (a < b || a == b)