Merge remote-tracking branch 'origin/master' into llvm11

master
Andrew Kelley 2020-08-04 17:09:40 -07:00
commit c6e0df6213
69 changed files with 6765 additions and 3265 deletions

View File

@ -77,6 +77,9 @@ pub fn build(b: *Builder) !void {
const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false;
if (link_libc) exe.linkLibC();
const log_scopes = b.option([]const []const u8, "log", "Which log scopes to enable") orelse &[0][]const u8{};
exe.addBuildOption([]const []const u8, "log_scopes", log_scopes);
exe.addBuildOption(bool, "enable_tracy", tracy != null);
if (tracy) |tracy_path| {
const client_cpp = fs.path.join(
@ -104,6 +107,12 @@ pub fn build(b: *Builder) !void {
const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false;
const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc");
test_stage2.addBuildOption(bool, "enable_qemu", is_qemu_enabled);
test_stage2.addBuildOption(bool, "enable_wine", is_wine_enabled);
test_stage2.addBuildOption(bool, "enable_wasmtime", is_wasmtime_enabled);
test_stage2.addBuildOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir);
const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests");
test_stage2_step.dependOn(&test_stage2.step);
test_step.dependOn(test_stage2_step);

View File

@ -41,7 +41,7 @@ jobs:
steps:
- powershell: |
(New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2020-06-02/msys2-base-x86_64-20200602.sfx.exe", "sfx.exe")
(New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2020-07-20/msys2-base-x86_64-20200720.sfx.exe", "sfx.exe")
.\sfx.exe -y -o\
del sfx.exe
displayName: Download/Extract/Install MSYS2

View File

@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documentation - The Zig Programming Language</title>
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg=="/>
<style>
@ -7473,7 +7474,7 @@ export fn @"A function name that is a complete sentence."() void {}
<p>
When looking at the resulting object, you can see the symbol is used verbatim:
</p>
<pre>00000000000001f0 T A function name that is a complete sentence.</pre>
<pre><code>00000000000001f0 T A function name that is a complete sentence.</code></pre>
{#see_also|Exporting a C Library#}
{#header_close#}

View File

@ -108,6 +108,33 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
mem.copy(T, self.items[i .. i + items.len], items);
}
/// Replace range of elements `list[start..start+len]` with `new_items`
/// grows list if `len < new_items.len`. may allocate
/// shrinks list if `len > new_items.len`
pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void {
const after_range = start + len;
const range = self.items[start..after_range];
if (range.len == new_items.len)
mem.copy(T, range, new_items)
else if (range.len < new_items.len) {
const first = new_items[0..range.len];
const rest = new_items[range.len..];
mem.copy(T, range, first);
try self.insertSlice(after_range, rest);
} else {
mem.copy(T, range, new_items);
const after_subrange = start + new_items.len;
for (self.items[after_range..]) |item, i| {
self.items[after_subrange..][i] = item;
}
self.items.len -= len - new_items.len;
}
}
/// Extend the list by 1 element. Allocates more memory as necessary.
pub fn append(self: *Self, item: T) !void {
const new_item_ptr = try self.addOne();
@ -189,6 +216,15 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
mem.set(T, self.items[old_len..self.items.len], value);
}
/// Append a value to the list `n` times.
/// Asserts the capacity is enough.
pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
const new_len = self.items.len + n;
assert(new_len <= self.capacity);
mem.set(T, self.items.ptr[self.items.len..new_len], value);
self.items.len = new_len;
}
/// Adjust the list's length to `new_len`.
/// Does not initialize added items if any.
pub fn resize(self: *Self, new_len: usize) !void {
@ -366,6 +402,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
mem.copy(T, self.items[i .. i + items.len], items);
}
/// Replace range of elements `list[start..start+len]` with `new_items`
/// grows list if `len < new_items.len`. may allocate
/// shrinks list if `len > new_items.len`
pub fn replaceRange(self: *Self, start: usize, len: usize, new_items: SliceConst) !void {
var managed = self.toManaged(allocator);
try managed.replaceRange(start, len, new_items);
self.* = managed.toUnmanaged();
}
/// Extend the list by 1 element. Allocates more memory as necessary.
pub fn append(self: *Self, allocator: *Allocator, item: T) !void {
const new_item_ptr = try self.addOne(allocator);
@ -437,6 +482,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
mem.set(T, self.items[old_len..self.items.len], value);
}
/// Append a value to the list `n` times.
/// Asserts the capacity is enough.
pub fn appendNTimesAssumeCapacity(self: *Self, value: T, n: usize) void {
const new_len = self.items.len + n;
assert(new_len <= self.capacity);
mem.set(T, self.items.ptr[self.items.len..new_len], value);
self.items.len = new_len;
}
/// Adjust the list's length to `new_len`.
/// Does not initialize added items if any.
pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void {
@ -714,6 +768,38 @@ test "std.ArrayList.insertSlice" {
testing.expect(list.items[0] == 1);
}
test "std.ArrayList.replaceRange" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const alloc = &arena.allocator;
const init = [_]i32{ 1, 2, 3, 4, 5 };
const new = [_]i32{ 0, 0, 0 };
var list_zero = ArrayList(i32).init(alloc);
var list_eq = ArrayList(i32).init(alloc);
var list_lt = ArrayList(i32).init(alloc);
var list_gt = ArrayList(i32).init(alloc);
try list_zero.appendSlice(&init);
try list_eq.appendSlice(&init);
try list_lt.appendSlice(&init);
try list_gt.appendSlice(&init);
try list_zero.replaceRange(1, 0, &new);
try list_eq.replaceRange(1, 3, &new);
try list_lt.replaceRange(1, 2, &new);
// after_range > new_items.len in function body
testing.expect(1 + 4 > new.len);
try list_gt.replaceRange(1, 4, &new);
testing.expectEqualSlices(i32, list_zero.items, &[_]i32{ 1, 0, 0, 0, 2, 3, 4, 5 });
testing.expectEqualSlices(i32, list_eq.items, &[_]i32{ 1, 0, 0, 0, 5 });
testing.expectEqualSlices(i32, list_lt.items, &[_]i32{ 1, 0, 0, 0, 4, 5 });
testing.expectEqualSlices(i32, list_gt.items, &[_]i32{ 1, 0, 0, 0 });
}
const Item = struct {
integer: i32,
sub_items: ArrayList(Item),

View File

@ -430,9 +430,9 @@ pub const Builder = struct {
const entry = self.user_input_options.getEntry(name) orelse return null;
entry.value.used = true;
switch (type_id) {
TypeId.Bool => switch (entry.value.value) {
UserValue.Flag => return true,
UserValue.Scalar => |s| {
.Bool => switch (entry.value.value) {
.Flag => return true,
.Scalar => |s| {
if (mem.eql(u8, s, "true")) {
return true;
} else if (mem.eql(u8, s, "false")) {
@ -443,21 +443,21 @@ pub const Builder = struct {
return null;
}
},
UserValue.List => {
.List => {
warn("Expected -D{} to be a boolean, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
},
TypeId.Int => panic("TODO integer options to build script", .{}),
TypeId.Float => panic("TODO float options to build script", .{}),
TypeId.Enum => switch (entry.value.value) {
UserValue.Flag => {
.Int => panic("TODO integer options to build script", .{}),
.Float => panic("TODO float options to build script", .{}),
.Enum => switch (entry.value.value) {
.Flag => {
warn("Expected -D{} to be a string, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.Scalar => |s| {
.Scalar => |s| {
if (std.meta.stringToEnum(T, s)) |enum_lit| {
return enum_lit;
} else {
@ -466,33 +466,35 @@ pub const Builder = struct {
return null;
}
},
UserValue.List => {
.List => {
warn("Expected -D{} to be a string, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
},
TypeId.String => switch (entry.value.value) {
UserValue.Flag => {
.String => switch (entry.value.value) {
.Flag => {
warn("Expected -D{} to be a string, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.List => {
.List => {
warn("Expected -D{} to be a string, but received a list.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.Scalar => |s| return s,
.Scalar => |s| return s,
},
TypeId.List => switch (entry.value.value) {
UserValue.Flag => {
.List => switch (entry.value.value) {
.Flag => {
warn("Expected -D{} to be a list, but received a boolean.\n", .{name});
self.markInvalidUserInput();
return null;
},
UserValue.Scalar => |s| return &[_][]const u8{s},
UserValue.List => |lst| return lst.span(),
.Scalar => |s| {
return self.allocator.dupe([]const u8, &[_][]const u8{s}) catch unreachable;
},
.List => |lst| return lst.span(),
},
}
}
@ -1151,6 +1153,7 @@ pub const LibExeObjStep = struct {
bundle_compiler_rt: bool,
disable_stack_probing: bool,
disable_sanitize_c: bool,
rdynamic: bool,
c_std: Builder.CStd,
override_lib_dir: ?[]const u8,
main_pkg_path: ?[]const u8,
@ -1311,6 +1314,7 @@ pub const LibExeObjStep = struct {
.bundle_compiler_rt = false,
.disable_stack_probing = false,
.disable_sanitize_c = false,
.rdynamic = false,
.output_dir = null,
.single_threaded = false,
.installed_path = null,
@ -1704,13 +1708,23 @@ pub const LibExeObjStep = struct {
pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void {
const out = self.build_options_contents.outStream();
if (T == []const []const u8) {
out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable;
for (value) |slice| {
out.writeAll(" ") catch unreachable;
std.zig.renderStringLiteral(slice, out) catch unreachable;
out.writeAll(",\n") catch unreachable;
}
out.writeAll("};\n") catch unreachable;
return;
}
switch (@typeInfo(T)) {
.Enum => |enum_info| {
out.print("const {} = enum {{\n", .{@typeName(T)}) catch unreachable;
out.print("pub const {} = enum {{\n", .{@typeName(T)}) catch unreachable;
inline for (enum_info.fields) |field| {
out.print(" {},\n", .{field.name}) catch unreachable;
}
out.print("}};\n", .{}) catch unreachable;
out.writeAll("};\n") catch unreachable;
},
else => {},
}
@ -1843,10 +1857,10 @@ pub const LibExeObjStep = struct {
zig_args.append(builder.zig_exe) catch unreachable;
const cmd = switch (self.kind) {
Kind.Lib => "build-lib",
Kind.Exe => "build-exe",
Kind.Obj => "build-obj",
Kind.Test => "test",
.Lib => "build-lib",
.Exe => "build-exe",
.Obj => "build-obj",
.Test => "test",
};
zig_args.append(cmd) catch unreachable;
@ -1994,6 +2008,9 @@ pub const LibExeObjStep = struct {
if (self.disable_sanitize_c) {
try zig_args.append("-fno-sanitize-c");
}
if (self.rdynamic) {
try zig_args.append("-rdynamic");
}
if (self.code_model != .default) {
try zig_args.append("-code-model");

View File

@ -46,9 +46,10 @@ const BinaryElfOutput = struct {
.segments = ArrayList(*BinaryElfSegment).init(allocator),
.sections = ArrayList(*BinaryElfSection).init(allocator),
};
const elf_hdrs = try std.elf.readAllHeaders(allocator, elf_file);
const elf_hdr = try std.elf.readHeader(elf_file);
for (elf_hdrs.section_headers) |section, i| {
var section_headers = elf_hdr.section_header_iterator(elf_file);
while (try section_headers.next()) |section| {
if (sectionValidForOutput(section)) {
const newSection = try allocator.create(BinaryElfSection);
@ -61,7 +62,8 @@ const BinaryElfOutput = struct {
}
}
for (elf_hdrs.program_headers) |phdr, i| {
var program_headers = elf_hdr.program_header_iterator(elf_file);
while (try program_headers.next()) |phdr| {
if (phdr.p_type == elf.PT_LOAD) {
const newSegment = try allocator.create(BinaryElfSegment);

View File

@ -4,6 +4,7 @@ const build = std.build;
const Step = build.Step;
const Builder = build.Builder;
const LibExeObjStep = build.LibExeObjStep;
const WriteFileStep = build.WriteFileStep;
const fs = std.fs;
const mem = std.mem;
const process = std.process;
@ -42,6 +43,10 @@ pub const RunStep = struct {
pub const Arg = union(enum) {
Artifact: *LibExeObjStep,
WriteFile: struct {
step: *WriteFileStep,
file_name: []const u8,
},
Bytes: []u8,
};
@ -62,6 +67,16 @@ pub const RunStep = struct {
self.step.dependOn(&artifact.step);
}
pub fn addWriteFileArg(self: *RunStep, write_file: *WriteFileStep, file_name: []const u8) void {
self.argv.append(Arg{
.WriteFile = .{
.step = write_file,
.file_name = file_name,
},
}) catch unreachable;
self.step.dependOn(&write_file.step);
}
pub fn addArg(self: *RunStep, arg: []const u8) void {
self.argv.append(Arg{ .Bytes = self.builder.dupe(arg) }) catch unreachable;
}
@ -142,6 +157,9 @@ pub const RunStep = struct {
for (self.argv.span()) |arg| {
switch (arg) {
Arg.Bytes => |bytes| try argv_list.append(bytes),
Arg.WriteFile => |file| {
try argv_list.append(file.step.getOutputPath(file.file_name));
},
Arg.Artifact => |artifact| {
if (artifact.target.isWindows()) {
// On Windows we don't have rpaths so we have to add .dll search paths to PATH

View File

@ -8,6 +8,10 @@ pub const Tokenizer = tokenizer.Tokenizer;
pub const parse = @import("c/parse.zig").parse;
pub const ast = @import("c/ast.zig");
test "" {
_ = tokenizer;
}
pub usingnamespace @import("os/bits.zig");
pub usingnamespace switch (std.Target.current.os.tag) {

View File

@ -1,19 +1,10 @@
const std = @import("std");
const mem = std.mem;
pub const Source = struct {
buffer: []const u8,
file_name: []const u8,
tokens: TokenList,
pub const TokenList = std.SegmentedList(Token, 64);
};
pub const Token = struct {
id: Id,
start: usize,
end: usize,
source: *Source,
pub const Id = union(enum) {
Invalid,
@ -251,31 +242,6 @@ pub const Token = struct {
}
};
pub fn eql(a: Token, b: Token) bool {
// do we really need this cast here
if (@as(@TagType(Id), a.id) != b.id) return false;
return mem.eql(u8, a.slice(), b.slice());
}
pub fn slice(tok: Token) []const u8 {
return tok.source.buffer[tok.start..tok.end];
}
pub const Keyword = struct {
bytes: []const u8,
id: Id,
hash: u32,
fn init(bytes: []const u8, id: Id) Keyword {
@setEvalBranchQuota(2000);
return .{
.bytes = bytes,
.id = id,
.hash = std.hash_map.hashString(bytes),
};
}
};
// TODO extensions
pub const keywords = std.ComptimeStringMap(Id, .{
.{ "auto", .Keyword_auto },
@ -355,26 +321,26 @@ pub const Token = struct {
}
pub const NumSuffix = enum {
None,
F,
L,
U,
LU,
LL,
LLU,
none,
f,
l,
u,
lu,
ll,
llu,
};
pub const StrKind = enum {
None,
Wide,
Utf8,
Utf16,
Utf32,
none,
wide,
utf_8,
utf_16,
utf_32,
};
};
pub const Tokenizer = struct {
source: *Source,
buffer: []const u8,
index: usize = 0,
prev_tok_id: @TagType(Token.Id) = .Invalid,
pp_directive: bool = false,
@ -385,7 +351,6 @@ pub const Tokenizer = struct {
.id = .Eof,
.start = self.index,
.end = undefined,
.source = self.source,
};
var state: enum {
Start,
@ -446,8 +411,8 @@ pub const Tokenizer = struct {
} = .Start;
var string = false;
var counter: u32 = 0;
while (self.index < self.source.buffer.len) : (self.index += 1) {
const c = self.source.buffer[self.index];
while (self.index < self.buffer.len) : (self.index += 1) {
const c = self.buffer[self.index];
switch (state) {
.Start => switch (c) {
'\n' => {
@ -460,11 +425,11 @@ pub const Tokenizer = struct {
state = .Cr;
},
'"' => {
result.id = .{ .StringLiteral = .None };
result.id = .{ .StringLiteral = .none };
state = .StringLiteral;
},
'\'' => {
result.id = .{ .CharLiteral = .None };
result.id = .{ .CharLiteral = .none };
state = .CharLiteralStart;
},
'u' => {
@ -641,11 +606,11 @@ pub const Tokenizer = struct {
state = .u8;
},
'\'' => {
result.id = .{ .CharLiteral = .Utf16 };
result.id = .{ .CharLiteral = .utf_16 };
state = .CharLiteralStart;
},
'\"' => {
result.id = .{ .StringLiteral = .Utf16 };
result.id = .{ .StringLiteral = .utf_16 };
state = .StringLiteral;
},
else => {
@ -655,7 +620,7 @@ pub const Tokenizer = struct {
},
.u8 => switch (c) {
'\"' => {
result.id = .{ .StringLiteral = .Utf8 };
result.id = .{ .StringLiteral = .utf_8 };
state = .StringLiteral;
},
else => {
@ -665,11 +630,11 @@ pub const Tokenizer = struct {
},
.U => switch (c) {
'\'' => {
result.id = .{ .CharLiteral = .Utf32 };
result.id = .{ .CharLiteral = .utf_32 };
state = .CharLiteralStart;
},
'\"' => {
result.id = .{ .StringLiteral = .Utf32 };
result.id = .{ .StringLiteral = .utf_32 };
state = .StringLiteral;
},
else => {
@ -679,11 +644,11 @@ pub const Tokenizer = struct {
},
.L => switch (c) {
'\'' => {
result.id = .{ .CharLiteral = .Wide };
result.id = .{ .CharLiteral = .wide };
state = .CharLiteralStart;
},
'\"' => {
result.id = .{ .StringLiteral = .Wide };
result.id = .{ .StringLiteral = .wide };
state = .StringLiteral;
},
else => {
@ -808,7 +773,7 @@ pub const Tokenizer = struct {
.Identifier => switch (c) {
'a'...'z', 'A'...'Z', '_', '0'...'9' => {},
else => {
result.id = Token.getKeyword(self.source.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
result.id = Token.getKeyword(self.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
if (self.prev_tok_id == .Hash)
self.pp_directive = true;
break;
@ -1137,7 +1102,7 @@ pub const Tokenizer = struct {
state = .IntegerSuffixL;
},
else => {
result.id = .{ .IntegerLiteral = .None };
result.id = .{ .IntegerLiteral = .none };
break;
},
},
@ -1146,7 +1111,7 @@ pub const Tokenizer = struct {
state = .IntegerSuffixUL;
},
else => {
result.id = .{ .IntegerLiteral = .U };
result.id = .{ .IntegerLiteral = .u };
break;
},
},
@ -1155,34 +1120,34 @@ pub const Tokenizer = struct {
state = .IntegerSuffixLL;
},
'u', 'U' => {
result.id = .{ .IntegerLiteral = .LU };
result.id = .{ .IntegerLiteral = .lu };
self.index += 1;
break;
},
else => {
result.id = .{ .IntegerLiteral = .L };
result.id = .{ .IntegerLiteral = .l };
break;
},
},
.IntegerSuffixLL => switch (c) {
'u', 'U' => {
result.id = .{ .IntegerLiteral = .LLU };
result.id = .{ .IntegerLiteral = .llu };
self.index += 1;
break;
},
else => {
result.id = .{ .IntegerLiteral = .LL };
result.id = .{ .IntegerLiteral = .ll };
break;
},
},
.IntegerSuffixUL => switch (c) {
'l', 'L' => {
result.id = .{ .IntegerLiteral = .LLU };
result.id = .{ .IntegerLiteral = .llu };
self.index += 1;
break;
},
else => {
result.id = .{ .IntegerLiteral = .LU };
result.id = .{ .IntegerLiteral = .lu };
break;
},
},
@ -1230,26 +1195,26 @@ pub const Tokenizer = struct {
},
.FloatSuffix => switch (c) {
'l', 'L' => {
result.id = .{ .FloatLiteral = .L };
result.id = .{ .FloatLiteral = .l };
self.index += 1;
break;
},
'f', 'F' => {
result.id = .{ .FloatLiteral = .F };
result.id = .{ .FloatLiteral = .f };
self.index += 1;
break;
},
else => {
result.id = .{ .FloatLiteral = .None };
result.id = .{ .FloatLiteral = .none };
break;
},
},
}
} else if (self.index == self.source.buffer.len) {
} else if (self.index == self.buffer.len) {
switch (state) {
.Start => {},
.u, .u8, .U, .L, .Identifier => {
result.id = Token.getKeyword(self.source.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
result.id = Token.getKeyword(self.buffer[result.start..self.index], self.prev_tok_id == .Hash and !self.pp_directive) orelse .Identifier;
},
.Cr,
@ -1270,11 +1235,11 @@ pub const Tokenizer = struct {
.MacroString,
=> result.id = .Invalid,
.FloatExponentDigits => result.id = if (counter == 0) .Invalid else .{ .FloatLiteral = .None },
.FloatExponentDigits => result.id = if (counter == 0) .Invalid else .{ .FloatLiteral = .none },
.FloatFraction,
.FloatFractionHex,
=> result.id = .{ .FloatLiteral = .None },
=> result.id = .{ .FloatLiteral = .none },
.IntegerLiteralOct,
.IntegerLiteralBinary,
@ -1282,13 +1247,13 @@ pub const Tokenizer = struct {
.IntegerLiteral,
.IntegerSuffix,
.Zero,
=> result.id = .{ .IntegerLiteral = .None },
.IntegerSuffixU => result.id = .{ .IntegerLiteral = .U },
.IntegerSuffixL => result.id = .{ .IntegerLiteral = .L },
.IntegerSuffixLL => result.id = .{ .IntegerLiteral = .LL },
.IntegerSuffixUL => result.id = .{ .IntegerLiteral = .LU },
=> result.id = .{ .IntegerLiteral = .none },
.IntegerSuffixU => result.id = .{ .IntegerLiteral = .u },
.IntegerSuffixL => result.id = .{ .IntegerLiteral = .l },
.IntegerSuffixLL => result.id = .{ .IntegerLiteral = .ll },
.IntegerSuffixUL => result.id = .{ .IntegerLiteral = .lu },
.FloatSuffix => result.id = .{ .FloatLiteral = .None },
.FloatSuffix => result.id = .{ .FloatLiteral = .none },
.Equal => result.id = .Equal,
.Bang => result.id = .Bang,
.Minus => result.id = .Minus,
@ -1466,7 +1431,7 @@ test "preprocessor keywords" {
.Hash,
.Identifier,
.AngleBracketLeft,
.{ .IntegerLiteral = .None },
.{ .IntegerLiteral = .none },
.Nl,
.Hash,
.Keyword_ifdef,
@ -1499,18 +1464,18 @@ test "line continuation" {
.Identifier,
.Identifier,
.Nl,
.{ .StringLiteral = .None },
.{ .StringLiteral = .none },
.Nl,
.Hash,
.Keyword_define,
.{ .StringLiteral = .None },
.{ .StringLiteral = .none },
.Nl,
.{ .StringLiteral = .None },
.{ .StringLiteral = .none },
.Nl,
.Hash,
.Keyword_define,
.{ .StringLiteral = .None },
.{ .StringLiteral = .None },
.{ .StringLiteral = .none },
.{ .StringLiteral = .none },
});
}
@ -1527,23 +1492,23 @@ test "string prefix" {
\\L'foo'
\\
, &[_]Token.Id{
.{ .StringLiteral = .None },
.{ .StringLiteral = .none },
.Nl,
.{ .StringLiteral = .Utf16 },
.{ .StringLiteral = .utf_16 },
.Nl,
.{ .StringLiteral = .Utf8 },
.{ .StringLiteral = .utf_8 },
.Nl,
.{ .StringLiteral = .Utf32 },
.{ .StringLiteral = .utf_32 },
.Nl,
.{ .StringLiteral = .Wide },
.{ .StringLiteral = .wide },
.Nl,
.{ .CharLiteral = .None },
.{ .CharLiteral = .none },
.Nl,
.{ .CharLiteral = .Utf16 },
.{ .CharLiteral = .utf_16 },
.Nl,
.{ .CharLiteral = .Utf32 },
.{ .CharLiteral = .utf_32 },
.Nl,
.{ .CharLiteral = .Wide },
.{ .CharLiteral = .wide },
.Nl,
});
}
@ -1555,33 +1520,29 @@ test "num suffixes" {
\\ 1u 1ul 1ull 1
\\
, &[_]Token.Id{
.{ .FloatLiteral = .F },
.{ .FloatLiteral = .L },
.{ .FloatLiteral = .None },
.{ .FloatLiteral = .None },
.{ .FloatLiteral = .None },
.{ .FloatLiteral = .f },
.{ .FloatLiteral = .l },
.{ .FloatLiteral = .none },
.{ .FloatLiteral = .none },
.{ .FloatLiteral = .none },
.Nl,
.{ .IntegerLiteral = .L },
.{ .IntegerLiteral = .LU },
.{ .IntegerLiteral = .LL },
.{ .IntegerLiteral = .LLU },
.{ .IntegerLiteral = .None },
.{ .IntegerLiteral = .l },
.{ .IntegerLiteral = .lu },
.{ .IntegerLiteral = .ll },
.{ .IntegerLiteral = .llu },
.{ .IntegerLiteral = .none },
.Nl,
.{ .IntegerLiteral = .U },
.{ .IntegerLiteral = .LU },
.{ .IntegerLiteral = .LLU },
.{ .IntegerLiteral = .None },
.{ .IntegerLiteral = .u },
.{ .IntegerLiteral = .lu },
.{ .IntegerLiteral = .llu },
.{ .IntegerLiteral = .none },
.Nl,
});
}
fn expectTokens(source: []const u8, expected_tokens: []const Token.Id) void {
var tokenizer = Tokenizer{
.source = &Source{
.buffer = source,
.file_name = undefined,
.tokens = undefined,
},
.buffer = source,
};
for (expected_tokens) |expected_token_id| {
const token = tokenizer.next();

View File

@ -364,6 +364,7 @@ pub const ChildProcess = struct {
error.FileTooBig => unreachable,
error.DeviceBusy => unreachable,
error.FileLocksNotSupported => unreachable,
error.BadPathName => unreachable, // Windows-only
else => |e| return e,
}
else
@ -480,25 +481,20 @@ pub const ChildProcess = struct {
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
// TODO use CreateFileW here since we are using a string literal for the path
const nul_handle = if (any_ignore)
windows.CreateFile(
"NUL",
windows.GENERIC_READ,
windows.FILE_SHARE_READ,
null,
windows.OPEN_EXISTING,
windows.FILE_ATTRIBUTE_NORMAL,
null,
) catch |err| switch (err) {
error.SharingViolation => unreachable, // not possible for "NUL"
windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{
.dir = std.fs.cwd().fd,
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
.share_access = windows.FILE_SHARE_READ,
.creation = windows.OPEN_EXISTING,
.io_mode = .blocking,
}) catch |err| switch (err) {
error.PathAlreadyExists => unreachable, // not possible for "NUL"
error.PipeBusy => unreachable, // not possible for "NUL"
error.InvalidUtf8 => unreachable, // not possible for "NUL"
error.BadPathName => unreachable, // not possible for "NUL"
error.FileNotFound => unreachable, // not possible for "NUL"
error.AccessDenied => unreachable, // not possible for "NUL"
error.NameTooLong => unreachable, // not possible for "NUL"
error.WouldBlock => unreachable, // not possible for "NUL"
else => |e| return e,
}
else

View File

@ -1,8 +1,7 @@
const mem = @import("../mem.zig");
const math = @import("../math.zig");
const endian = @import("../endian.zig");
const debug = @import("../debug.zig");
const builtin = @import("builtin");
const debug = @import("../debug.zig");
const math = @import("../math.zig");
const htest = @import("test.zig");
const RoundParam = struct {
@ -31,7 +30,7 @@ fn Rp(a: usize, b: usize, c: usize, d: usize, x: usize, y: usize) RoundParam {
pub const Blake2s224 = Blake2s(224);
pub const Blake2s256 = Blake2s(256);
fn Blake2s(comptime out_len: usize) type {
pub fn Blake2s(comptime out_len: usize) type {
return struct {
const Self = @This();
pub const block_length = 64;
@ -67,10 +66,17 @@ fn Blake2s(comptime out_len: usize) type {
buf: [64]u8,
buf_len: u8,
key: []const u8,
pub fn init() Self {
return init_keyed("");
}
pub fn init_keyed(key: []const u8) Self {
debug.assert(8 <= out_len and out_len <= 512);
var s: Self = undefined;
s.key = key;
s.reset();
return s;
}
@ -78,14 +84,24 @@ fn Blake2s(comptime out_len: usize) type {
pub fn reset(d: *Self) void {
mem.copy(u32, d.h[0..], iv[0..]);
// No key plus default parameters
d.h[0] ^= 0x01010000 ^ @intCast(u32, out_len >> 3);
// default parameters
d.h[0] ^= 0x01010000 ^ @truncate(u32, d.key.len << 8) ^ @intCast(u32, out_len >> 3);
d.t = 0;
d.buf_len = 0;
if (d.key.len > 0) {
mem.set(u8, d.buf[d.key.len..], 0);
d.update(d.key);
d.buf_len = 64;
}
}
pub fn hash(b: []const u8, out: []u8) void {
var d = Self.init();
Self.hash_keyed("", b, out);
}
pub fn hash_keyed(key: []const u8, b: []const u8, out: []u8) void {
var d = Self.init_keyed(key);
d.update(b);
d.final(out);
}
@ -94,7 +110,7 @@ fn Blake2s(comptime out_len: usize) type {
var off: usize = 0;
// Partial buffer exists from previous update. Copy into buffer then hash.
if (d.buf_len != 0 and d.buf_len + b.len >= 64) {
if (d.buf_len != 0 and d.buf_len + b.len > 64) {
off += 64 - d.buf_len;
mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
d.t += 64;
@ -103,7 +119,7 @@ fn Blake2s(comptime out_len: usize) type {
}
// Full middle blocks.
while (off + 64 <= b.len) : (off += 64) {
while (off + 64 < b.len) : (off += 64) {
d.t += 64;
d.round(b[off .. off + 64], false);
}
@ -123,7 +139,7 @@ fn Blake2s(comptime out_len: usize) type {
const rr = d.h[0 .. out_len / 32];
for (rr) |s, j| {
mem.writeIntLittle(u32, out[4 * j ..][0..4], s);
mem.writeIntSliceLittle(u32, out[4 * j ..], s);
}
}
@ -188,6 +204,9 @@ test "blake2s224 single" {
const h3 = "e4e5cb6c7cae41982b397bf7b7d2d9d1949823ae78435326e8db4912";
htest.assertEqualHash(Blake2s224, h3, "The quick brown fox jumps over the lazy dog");
const h4 = "557381a78facd2b298640f4e32113e58967d61420af1aa939d0cfe01";
htest.assertEqualHash(Blake2s224, h4, "a" ** 32 ++ "b" ** 32);
}
test "blake2s224 streaming" {
@ -212,6 +231,37 @@ test "blake2s224 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
const h3 = "557381a78facd2b298640f4e32113e58967d61420af1aa939d0cfe01";
h.reset();
h.update("a" ** 32);
h.update("b" ** 32);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
h.reset();
h.update("a" ** 32 ++ "b" ** 32);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
}
test "comptime blake2s224" {
comptime {
@setEvalBranchQuota(6000);
var block = [_]u8{0} ** Blake2s224.block_length;
var out: [Blake2s224.digest_length]u8 = undefined;
const h1 = "86b7611563293f8c73627df7a6d6ba25ca0548c2a6481f7d116ee576";
htest.assertEqualHash(Blake2s224, h1, block[0..]);
var h = Blake2s224.init();
h.update(&block);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
}
}
test "blake2s256 single" {
@ -223,6 +273,9 @@ test "blake2s256 single" {
const h3 = "606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812";
htest.assertEqualHash(Blake2s256, h3, "The quick brown fox jumps over the lazy dog");
const h4 = "8d8711dade07a6b92b9a3ea1f40bee9b2c53ff3edd2a273dec170b0163568977";
htest.assertEqualHash(Blake2s256, h4, "a" ** 32 ++ "b" ** 32);
}
test "blake2s256 streaming" {
@ -247,15 +300,60 @@ test "blake2s256 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
const h3 = "8d8711dade07a6b92b9a3ea1f40bee9b2c53ff3edd2a273dec170b0163568977";
h.reset();
h.update("a" ** 32);
h.update("b" ** 32);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
h.reset();
h.update("a" ** 32 ++ "b" ** 32);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
}
test "blake2s256 aligned final" {
var block = [_]u8{0} ** Blake2s256.block_length;
var out: [Blake2s256.digest_length]u8 = undefined;
test "blake2s256 keyed" {
var out: [32]u8 = undefined;
var h = Blake2s256.init();
h.update(&block);
const h1 = "10f918da4d74fab3302e48a5d67d03804b1ec95372a62a0f33b7c9fa28ba1ae6";
const key = "secret_key";
Blake2s256.hash_keyed(key, "a" ** 64 ++ "b" ** 64, &out);
htest.assertEqual(h1, out[0..]);
var h = Blake2s256.init_keyed(key);
h.update("a" ** 64 ++ "b" ** 64);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
h.reset();
h.update("a" ** 64);
h.update("b" ** 64);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
}
test "comptime blake2s256" {
comptime {
@setEvalBranchQuota(6000);
var block = [_]u8{0} ** Blake2s256.block_length;
var out: [Blake2s256.digest_length]u8 = undefined;
const h1 = "ae09db7cd54f42b490ef09b6bc541af688e4959bb8c53f359a6f56e38ab454a3";
htest.assertEqualHash(Blake2s256, h1, block[0..]);
var h = Blake2s256.init();
h.update(&block);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
}
}
/////////////////////
@ -264,7 +362,7 @@ test "blake2s256 aligned final" {
pub const Blake2b384 = Blake2b(384);
pub const Blake2b512 = Blake2b(512);
fn Blake2b(comptime out_len: usize) type {
pub fn Blake2b(comptime out_len: usize) type {
return struct {
const Self = @This();
pub const block_length = 128;
@ -302,10 +400,17 @@ fn Blake2b(comptime out_len: usize) type {
buf: [128]u8,
buf_len: u8,
key: []const u8,
pub fn init() Self {
return init_keyed("");
}
pub fn init_keyed(key: []const u8) Self {
debug.assert(8 <= out_len and out_len <= 512);
var s: Self = undefined;
s.key = key;
s.reset();
return s;
}
@ -313,14 +418,24 @@ fn Blake2b(comptime out_len: usize) type {
pub fn reset(d: *Self) void {
mem.copy(u64, d.h[0..], iv[0..]);
// No key plus default parameters
d.h[0] ^= 0x01010000 ^ (out_len >> 3);
// default parameters
d.h[0] ^= 0x01010000 ^ (d.key.len << 8) ^ (out_len >> 3);
d.t = 0;
d.buf_len = 0;
if (d.key.len > 0) {
mem.set(u8, d.buf[d.key.len..], 0);
d.update(d.key);
d.buf_len = 128;
}
}
pub fn hash(b: []const u8, out: []u8) void {
var d = Self.init();
Self.hash_keyed("", b, out);
}
pub fn hash_keyed(key: []const u8, b: []const u8, out: []u8) void {
var d = Self.init_keyed(key);
d.update(b);
d.final(out);
}
@ -329,7 +444,7 @@ fn Blake2b(comptime out_len: usize) type {
var off: usize = 0;
// Partial buffer exists from previous update. Copy into buffer then hash.
if (d.buf_len != 0 and d.buf_len + b.len >= 128) {
if (d.buf_len != 0 and d.buf_len + b.len > 128) {
off += 128 - d.buf_len;
mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
d.t += 128;
@ -338,7 +453,7 @@ fn Blake2b(comptime out_len: usize) type {
}
// Full middle blocks.
while (off + 128 <= b.len) : (off += 128) {
while (off + 128 < b.len) : (off += 128) {
d.t += 128;
d.round(b[off .. off + 128], false);
}
@ -356,7 +471,7 @@ fn Blake2b(comptime out_len: usize) type {
const rr = d.h[0 .. out_len / 64];
for (rr) |s, j| {
mem.writeIntLittle(u64, out[8 * j ..][0..8], s);
mem.writeIntSliceLittle(u64, out[8 * j ..], s);
}
}
@ -421,6 +536,9 @@ test "blake2b384 single" {
const h3 = "b7c81b228b6bd912930e8f0b5387989691c1cee1e65aade4da3b86a3c9f678fc8018f6ed9e2906720c8d2a3aeda9c03d";
htest.assertEqualHash(Blake2b384, h3, "The quick brown fox jumps over the lazy dog");
const h4 = "b7283f0172fecbbd7eca32ce10d8a6c06b453cb3cf675b33eb4246f0da2bb94a6c0bdd6eec0b5fd71ec4fd51be80bf4c";
htest.assertEqualHash(Blake2b384, h4, "a" ** 64 ++ "b" ** 64);
}
test "blake2b384 streaming" {
@ -445,6 +563,37 @@ test "blake2b384 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
const h3 = "b7283f0172fecbbd7eca32ce10d8a6c06b453cb3cf675b33eb4246f0da2bb94a6c0bdd6eec0b5fd71ec4fd51be80bf4c";
h.reset();
h.update("a" ** 64 ++ "b" ** 64);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
h.reset();
h.update("a" ** 64);
h.update("b" ** 64);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
}
test "comptime blake2b384" {
comptime {
@setEvalBranchQuota(7000);
var block = [_]u8{0} ** Blake2b384.block_length;
var out: [Blake2b384.digest_length]u8 = undefined;
const h1 = "e8aa1931ea0422e4446fecdd25c16cf35c240b10cb4659dd5c776eddcaa4d922397a589404b46eb2e53d78132d05fd7d";
htest.assertEqualHash(Blake2b384, h1, block[0..]);
var h = Blake2b384.init();
h.update(&block);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
}
}
test "blake2b512 single" {
@ -456,6 +605,9 @@ test "blake2b512 single" {
const h3 = "a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918";
htest.assertEqualHash(Blake2b512, h3, "The quick brown fox jumps over the lazy dog");
const h4 = "049980af04d6a2cf16b4b49793c3ed7e40732073788806f2c989ebe9547bda0541d63abe298ec8955d08af48ae731f2e8a0bd6d201655a5473b4aa79d211b920";
htest.assertEqualHash(Blake2b512, h4, "a" ** 64 ++ "b" ** 64);
}
test "blake2b512 streaming" {
@ -480,13 +632,58 @@ test "blake2b512 streaming" {
h.update("c");
h.final(out[0..]);
htest.assertEqual(h2, out[0..]);
}
test "blake2b512 aligned final" {
var block = [_]u8{0} ** Blake2b512.block_length;
var out: [Blake2b512.digest_length]u8 = undefined;
const h3 = "049980af04d6a2cf16b4b49793c3ed7e40732073788806f2c989ebe9547bda0541d63abe298ec8955d08af48ae731f2e8a0bd6d201655a5473b4aa79d211b920";
var h = Blake2b512.init();
h.update(&block);
h.reset();
h.update("a" ** 64 ++ "b" ** 64);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
h.reset();
h.update("a" ** 64);
h.update("b" ** 64);
h.final(out[0..]);
htest.assertEqual(h3, out[0..]);
}
test "blake2b512 keyed" {
var out: [64]u8 = undefined;
const h1 = "8a978060ccaf582f388f37454363071ac9a67e3a704585fd879fb8a419a447e389c7c6de790faa20a7a7dccf197de736bc5b40b98a930b36df5bee7555750c4d";
const key = "secret_key";
Blake2b512.hash_keyed(key, "a" ** 64 ++ "b" ** 64, &out);
htest.assertEqual(h1, out[0..]);
var h = Blake2b512.init_keyed(key);
h.update("a" ** 64 ++ "b" ** 64);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
h.reset();
h.update("a" ** 64);
h.update("b" ** 64);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
}
test "comptime blake2b512" {
comptime {
@setEvalBranchQuota(8000);
var block = [_]u8{0} ** Blake2b512.block_length;
var out: [Blake2b512.digest_length]u8 = undefined;
const h1 = "865939e120e6805438478841afb739ae4250cf372653078a065cdcfffca4caf798e6d462b65d658fc165782640eded70963449ae1500fb0f24981d7727e22c41";
htest.assertEqualHash(Blake2b512, h1, block[0..]);
var h = Blake2b512.init();
h.update(&block);
h.final(out[0..]);
htest.assertEqual(h1, out[0..]);
}
}

View File

@ -159,6 +159,50 @@ pub fn writeILEB128Mem(ptr: []u8, int_value: anytype) !usize {
return buf.pos;
}
/// This is an "advanced" function. It allows one to use a fixed amount of memory to store a
/// ULEB128. This defeats the entire purpose of using this data encoding; it will no longer use
/// fewer bytes to store smaller numbers. The advantage of using a fixed width is that it makes
/// fields have a predictable size and so depending on the use case this tradeoff can be worthwhile.
/// An example use case of this is in emitting DWARF info where one wants to make a ULEB128 field
/// "relocatable", meaning that it becomes possible to later go back and patch the number to be a
/// different value without shifting all the following code.
pub fn writeUnsignedFixed(comptime l: usize, ptr: *[l]u8, int: std.meta.Int(false, l * 7)) void {
const T = @TypeOf(int);
const U = if (T.bit_count < 8) u8 else T;
var value = @intCast(U, int);
comptime var i = 0;
inline while (i < (l - 1)) : (i += 1) {
const byte = @truncate(u8, value) | 0b1000_0000;
value >>= 7;
ptr[i] = byte;
}
ptr[i] = @truncate(u8, value);
}
test "writeUnsignedFixed" {
{
var buf: [4]u8 = undefined;
writeUnsignedFixed(4, &buf, 0);
testing.expect((try test_read_uleb128(u64, &buf)) == 0);
}
{
var buf: [4]u8 = undefined;
writeUnsignedFixed(4, &buf, 1);
testing.expect((try test_read_uleb128(u64, &buf)) == 1);
}
{
var buf: [4]u8 = undefined;
writeUnsignedFixed(4, &buf, 1000);
testing.expect((try test_read_uleb128(u64, &buf)) == 1000);
}
{
var buf: [4]u8 = undefined;
writeUnsignedFixed(4, &buf, 10000000);
testing.expect((try test_read_uleb128(u64, &buf)) == 10000000);
}
}
// tests
fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T {
var reader = std.io.fixedBufferStream(encoded);

View File

@ -9,7 +9,7 @@ const leb = @import("debug/leb128.zig");
const ArrayList = std.ArrayList;
usingnamespace @import("dwarf_bits.zig");
pub usingnamespace @import("dwarf_bits.zig");
const PcRange = struct {
start: u64,

View File

@ -680,3 +680,20 @@ pub const LANG_HP_Basic91 = 0x8004;
pub const LANG_HP_Pascal91 = 0x8005;
pub const LANG_HP_IMacro = 0x8006;
pub const LANG_HP_Assembler = 0x8007;
pub const UT_compile = 0x01;
pub const UT_type = 0x02;
pub const UT_partial = 0x03;
pub const UT_skeleton = 0x04;
pub const UT_split_compile = 0x05;
pub const UT_split_type = 0x06;
pub const UT_lo_user = 0x80;
pub const UT_hi_user = 0xff;
pub const LNCT_path = 0x1;
pub const LNCT_directory_index = 0x2;
pub const LNCT_timestamp = 0x3;
pub const LNCT_size = 0x4;
pub const LNCT_MD5 = 0x5;
pub const LNCT_lo_user = 0x2000;
pub const LNCT_hi_user = 0x3fff;

View File

@ -341,6 +341,20 @@ const Header = struct {
shentsize: u16,
shnum: u16,
shstrndx: u16,
pub fn program_header_iterator(self: Header, file: File) ProgramHeaderIterator {
return .{
.elf_header = self,
.file = file,
};
}
pub fn section_header_iterator(self: Header, file: File) SectionHeaderIterator {
return .{
.elf_header = self,
.file = file,
};
}
};
pub fn readHeader(file: File) !Header {
@ -378,144 +392,137 @@ pub fn readHeader(file: File) !Header {
});
}
/// All integers are native endian.
pub const AllHeaders = struct {
header: Header,
section_headers: []Elf64_Shdr,
program_headers: []Elf64_Phdr,
allocator: *mem.Allocator,
pub const ProgramHeaderIterator = struct {
elf_header: Header,
file: File,
index: usize = 0,
pub fn next(self: *ProgramHeaderIterator) !?Elf64_Phdr {
if (self.index >= self.elf_header.phnum) return null;
defer self.index += 1;
if (self.elf_header.is_64) {
var phdr: Elf64_Phdr = undefined;
const offset = self.elf_header.phoff + @sizeOf(@TypeOf(phdr)) * self.index;
try preadNoEof(self.file, mem.asBytes(&phdr), offset);
// ELF endianness matches native endianness.
if (self.elf_header.endian == std.builtin.endian) return phdr;
// Convert fields to native endianness.
return Elf64_Phdr{
.p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type),
.p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset),
.p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr),
.p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr),
.p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz),
.p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz),
.p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags),
.p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align),
};
}
var phdr: Elf32_Phdr = undefined;
const offset = self.elf_header.phoff + @sizeOf(@TypeOf(phdr)) * self.index;
try preadNoEof(self.file, mem.asBytes(&phdr), offset);
// ELF endianness does NOT match native endianness.
if (self.elf_header.endian != std.builtin.endian) {
// Convert fields to native endianness.
phdr = .{
.p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type),
.p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset),
.p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr),
.p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr),
.p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz),
.p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz),
.p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags),
.p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align),
};
}
// Convert 32-bit header to 64-bit.
return Elf64_Phdr{
.p_type = phdr.p_type,
.p_offset = phdr.p_offset,
.p_vaddr = phdr.p_vaddr,
.p_paddr = phdr.p_paddr,
.p_filesz = phdr.p_filesz,
.p_memsz = phdr.p_memsz,
.p_flags = phdr.p_flags,
.p_align = phdr.p_align,
};
}
};
pub fn readAllHeaders(allocator: *mem.Allocator, file: File) !AllHeaders {
var hdrs: AllHeaders = .{
.allocator = allocator,
.header = try readHeader(file),
.section_headers = undefined,
.program_headers = undefined,
};
const is_64 = hdrs.header.is_64;
const need_bswap = hdrs.header.endian != std.builtin.endian;
pub const SectionHeaderIterator = struct {
elf_header: Header,
file: File,
index: usize = 0,
hdrs.section_headers = try allocator.alloc(Elf64_Shdr, hdrs.header.shnum);
errdefer allocator.free(hdrs.section_headers);
pub fn next(self: *SectionHeaderIterator) !?Elf64_Shdr {
if (self.index >= self.elf_header.shnum) return null;
defer self.index += 1;
hdrs.program_headers = try allocator.alloc(Elf64_Phdr, hdrs.header.phnum);
errdefer allocator.free(hdrs.program_headers);
if (self.elf_header.is_64) {
var shdr: Elf64_Shdr = undefined;
const offset = self.elf_header.phoff + @sizeOf(@TypeOf(shdr)) * self.index;
try preadNoEof(self.file, mem.asBytes(&shdr), offset);
// If the ELF file is 64-bit and same-endianness, then all we have to do is
// yeet the bytes into memory.
// If only the endianness is different, they can be simply byte swapped.
if (is_64) {
const shdr_buf = std.mem.sliceAsBytes(hdrs.section_headers);
const phdr_buf = std.mem.sliceAsBytes(hdrs.program_headers);
try preadNoEof(file, shdr_buf, hdrs.header.shoff);
try preadNoEof(file, phdr_buf, hdrs.header.phoff);
// ELF endianness matches native endianness.
if (self.elf_header.endian == std.builtin.endian) return shdr;
if (need_bswap) {
for (hdrs.section_headers) |*shdr| {
shdr.* = .{
.sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name),
.sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type),
.sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags),
.sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr),
.sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset),
.sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size),
.sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link),
.sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info),
.sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign),
.sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize),
};
}
for (hdrs.program_headers) |*phdr| {
phdr.* = .{
.p_type = @byteSwap(@TypeOf(phdr.p_type), phdr.p_type),
.p_offset = @byteSwap(@TypeOf(phdr.p_offset), phdr.p_offset),
.p_vaddr = @byteSwap(@TypeOf(phdr.p_vaddr), phdr.p_vaddr),
.p_paddr = @byteSwap(@TypeOf(phdr.p_paddr), phdr.p_paddr),
.p_filesz = @byteSwap(@TypeOf(phdr.p_filesz), phdr.p_filesz),
.p_memsz = @byteSwap(@TypeOf(phdr.p_memsz), phdr.p_memsz),
.p_flags = @byteSwap(@TypeOf(phdr.p_flags), phdr.p_flags),
.p_align = @byteSwap(@TypeOf(phdr.p_align), phdr.p_align),
};
}
// Convert fields to native endianness.
return Elf64_Shdr{
.sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name),
.sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type),
.sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags),
.sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr),
.sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset),
.sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size),
.sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link),
.sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info),
.sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign),
.sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize),
};
}
return hdrs;
var shdr: Elf32_Shdr = undefined;
const offset = self.elf_header.shoff + @sizeOf(@TypeOf(shdr)) * self.index;
try preadNoEof(self.file, mem.asBytes(&shdr), offset);
// ELF endianness does NOT match native endianness.
if (self.elf_header.endian != std.builtin.endian) {
// Convert fields to native endianness.
shdr = .{
.sh_name = @byteSwap(@TypeOf(shdr.sh_name), shdr.sh_name),
.sh_type = @byteSwap(@TypeOf(shdr.sh_type), shdr.sh_type),
.sh_flags = @byteSwap(@TypeOf(shdr.sh_flags), shdr.sh_flags),
.sh_addr = @byteSwap(@TypeOf(shdr.sh_addr), shdr.sh_addr),
.sh_offset = @byteSwap(@TypeOf(shdr.sh_offset), shdr.sh_offset),
.sh_size = @byteSwap(@TypeOf(shdr.sh_size), shdr.sh_size),
.sh_link = @byteSwap(@TypeOf(shdr.sh_link), shdr.sh_link),
.sh_info = @byteSwap(@TypeOf(shdr.sh_info), shdr.sh_info),
.sh_addralign = @byteSwap(@TypeOf(shdr.sh_addralign), shdr.sh_addralign),
.sh_entsize = @byteSwap(@TypeOf(shdr.sh_entsize), shdr.sh_entsize),
};
}
// Convert 32-bit header to 64-bit.
return Elf64_Shdr{
.sh_name = shdr.sh_name,
.sh_type = shdr.sh_type,
.sh_flags = shdr.sh_flags,
.sh_addr = shdr.sh_addr,
.sh_offset = shdr.sh_offset,
.sh_size = shdr.sh_size,
.sh_link = shdr.sh_link,
.sh_info = shdr.sh_info,
.sh_addralign = shdr.sh_addralign,
.sh_entsize = shdr.sh_entsize,
};
}
const shdrs_32 = try allocator.alloc(Elf32_Shdr, hdrs.header.shnum);
defer allocator.free(shdrs_32);
const phdrs_32 = try allocator.alloc(Elf32_Phdr, hdrs.header.phnum);
defer allocator.free(phdrs_32);
const shdr_buf = std.mem.sliceAsBytes(shdrs_32);
const phdr_buf = std.mem.sliceAsBytes(phdrs_32);
try preadNoEof(file, shdr_buf, hdrs.header.shoff);
try preadNoEof(file, phdr_buf, hdrs.header.phoff);
if (need_bswap) {
for (hdrs.section_headers) |*shdr, i| {
const o = shdrs_32[i];
shdr.* = .{
.sh_name = @byteSwap(@TypeOf(o.sh_name), o.sh_name),
.sh_type = @byteSwap(@TypeOf(o.sh_type), o.sh_type),
.sh_flags = @byteSwap(@TypeOf(o.sh_flags), o.sh_flags),
.sh_addr = @byteSwap(@TypeOf(o.sh_addr), o.sh_addr),
.sh_offset = @byteSwap(@TypeOf(o.sh_offset), o.sh_offset),
.sh_size = @byteSwap(@TypeOf(o.sh_size), o.sh_size),
.sh_link = @byteSwap(@TypeOf(o.sh_link), o.sh_link),
.sh_info = @byteSwap(@TypeOf(o.sh_info), o.sh_info),
.sh_addralign = @byteSwap(@TypeOf(o.sh_addralign), o.sh_addralign),
.sh_entsize = @byteSwap(@TypeOf(o.sh_entsize), o.sh_entsize),
};
}
for (hdrs.program_headers) |*phdr, i| {
const o = phdrs_32[i];
phdr.* = .{
.p_type = @byteSwap(@TypeOf(o.p_type), o.p_type),
.p_offset = @byteSwap(@TypeOf(o.p_offset), o.p_offset),
.p_vaddr = @byteSwap(@TypeOf(o.p_vaddr), o.p_vaddr),
.p_paddr = @byteSwap(@TypeOf(o.p_paddr), o.p_paddr),
.p_filesz = @byteSwap(@TypeOf(o.p_filesz), o.p_filesz),
.p_memsz = @byteSwap(@TypeOf(o.p_memsz), o.p_memsz),
.p_flags = @byteSwap(@TypeOf(o.p_flags), o.p_flags),
.p_align = @byteSwap(@TypeOf(o.p_align), o.p_align),
};
}
} else {
for (hdrs.section_headers) |*shdr, i| {
const o = shdrs_32[i];
shdr.* = .{
.sh_name = o.sh_name,
.sh_type = o.sh_type,
.sh_flags = o.sh_flags,
.sh_addr = o.sh_addr,
.sh_offset = o.sh_offset,
.sh_size = o.sh_size,
.sh_link = o.sh_link,
.sh_info = o.sh_info,
.sh_addralign = o.sh_addralign,
.sh_entsize = o.sh_entsize,
};
}
for (hdrs.program_headers) |*phdr, i| {
const o = phdrs_32[i];
phdr.* = .{
.p_type = o.p_type,
.p_offset = o.p_offset,
.p_vaddr = o.p_vaddr,
.p_paddr = o.p_paddr,
.p_filesz = o.p_filesz,
.p_memsz = o.p_memsz,
.p_flags = o.p_flags,
.p_align = o.p_align,
};
}
}
return hdrs;
}
};
pub fn int(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) {
if (is_64) {
@ -538,7 +545,7 @@ pub fn int32(need_bswap: bool, int_32: anytype, comptime Int64: anytype) Int64 {
}
fn preadNoEof(file: std.fs.File, buf: []u8, offset: u64) !void {
var i: u64 = 0;
var i: usize = 0;
while (i < buf.len) {
const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) {
error.SystemResources => return error.SystemResources,

View File

@ -224,6 +224,7 @@ pub fn LinearFifo(
pub fn reader(self: *Self) std.io.Reader(*Self, error{}, readFn) {
return .{ .context = self };
}
/// Deprecated: `use reader`
pub fn inStream(self: *Self) std.io.InStream(*Self, error{}, readFn) {
return .{ .context = self };
@ -315,6 +316,11 @@ pub fn LinearFifo(
return bytes.len;
}
pub fn writer(self: *Self) std.io.Writer(*Self, error{OutOfMemory}, appendWrite) {
return .{ .context = self };
}
/// Deprecated: `use writer`
pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) {
return .{ .context = self };
}
@ -426,14 +432,14 @@ test "LinearFifo(u8, .Dynamic)" {
fifo.shrink(0);
{
try fifo.outStream().print("{}, {}!", .{ "Hello", "World" });
try fifo.writer().print("{}, {}!", .{ "Hello", "World" });
var result: [30]u8 = undefined;
testing.expectEqualSlices(u8, "Hello, World!", result[0..fifo.read(&result)]);
testing.expectEqual(@as(usize, 0), fifo.readableLength());
}
{
try fifo.outStream().writeAll("This is a test");
try fifo.writer().writeAll("This is a test");
var result: [30]u8 = undefined;
testing.expectEqualSlices(u8, "This", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);
testing.expectEqualSlices(u8, "is", (try fifo.reader().readUntilDelimiterOrEof(&result, ' ')).?);

View File

@ -88,6 +88,8 @@ pub fn format(
if (args.len > ArgSetType.bit_count) {
@compileError("32 arguments max are supported per format call");
}
if (args.len == 0)
return writer.writeAll(fmt);
const State = enum {
Start,

View File

@ -84,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
try crypto.randomBytes(rand_buf[0..]);
base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
if (cwd().symLink(existing_path, new_path, .{})) {
if (cwd().symLink(existing_path, tmp_path, .{})) {
return rename(tmp_path, new_path);
} else |err| switch (err) {
error.PathAlreadyExists => continue,
@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
assert(path.isAbsoluteWindowsW(absolute_path_w));
const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null);
os.windows.CloseHandle(handle);
return os.mkdirW(absolute_path_w, default_new_dir_mode);
}
pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute");
@ -881,8 +880,7 @@ pub const Dir = struct {
}
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null);
os.windows.CloseHandle(handle);
try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
}
/// Calls makeDir recursively to make an entire path. Returns success if the path
@ -1119,7 +1117,7 @@ pub const Dir = struct {
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.deleteFileW(sub_path_w.span().ptr);
return self.deleteFileW(sub_path_w.span());
} else if (builtin.os.tag == .wasi) {
os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
@ -1153,7 +1151,7 @@ pub const Dir = struct {
}
/// Same as `deleteFile` except the parameter is WTF-16 encoded.
pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void {
pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
else => |e| return e,
@ -1182,7 +1180,7 @@ pub const Dir = struct {
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.deleteDirW(sub_path_w.span().ptr);
return self.deleteDirW(sub_path_w.span());
} else if (builtin.os.tag == .wasi) {
os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
@ -1204,7 +1202,7 @@ pub const Dir = struct {
/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
/// This function is Windows-only.
pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void {
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
else => |e| return e,
@ -1263,11 +1261,11 @@ pub const Dir = struct {
/// are null-terminated, WTF16 encoded.
pub fn symLinkW(
self: Dir,
target_path_w: [:0]const u16,
sym_link_path_w: [:0]const u16,
target_path_w: []const u16,
sym_link_path_w: []const u16,
flags: SymLinkFlags,
) !void {
return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
}
/// Read value of a symbolic link.
@ -1278,7 +1276,8 @@ pub const Dir = struct {
return self.readLinkWasi(sub_path, buffer);
}
if (builtin.os.tag == .windows) {
return os.windows.ReadLink(self.fd, sub_path, buffer);
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.readLinkW(sub_path_w.span(), buffer);
}
const sub_path_c = try os.toPosixPath(sub_path);
return self.readLinkZ(&sub_path_c, buffer);
@ -1295,15 +1294,15 @@ pub const Dir = struct {
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
return self.readLinkW(sub_path_w, buffer);
return self.readLinkW(sub_path_w.span(), buffer);
}
return os.readlinkatZ(self.fd, sub_path_c, buffer);
}
/// Windows-only. Same as `readLink` except the pathname parameter
/// is null-terminated, WTF16 encoded.
pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 {
return os.windows.ReadLinkW(self.fd, sub_path_w, buffer);
pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
return os.windows.ReadLink(self.fd, sub_path_w, buffer);
}
/// On success, caller owns returned buffer.
@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
assert(path.isAbsolute(target_path));
assert(path.isAbsolute(sym_link_path));
if (builtin.os.tag == .windows) {
return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory);
const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlink(target_path, sym_link_path);
}
@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
/// Note that this function will by default try creating a symbolic link to a file. If you would
/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {
assert(path.isAbsoluteWindowsW(target_path_w));
assert(path.isAbsoluteWindowsW(sym_link_path_w));
return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory);
pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
assert(path.isAbsoluteWindowsWTF16(target_path_w));
assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
}
/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
@ -1836,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons
if (builtin.os.tag == .windows) {
const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory);
return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
}
return os.symlinkZ(target_path_c, sym_link_path_c);
}
@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
return walker;
}
pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;
pub const OpenSelfExeError = error{
SharingViolation,
PathAlreadyExists,
FileNotFound,
AccessDenied,
PipeBusy,
NameTooLong,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
Unexpected,
} || os.OpenError || SelfExePathError || os.FlockError;
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
if (builtin.os.tag == .linux) {

View File

@ -47,7 +47,20 @@ pub const File = struct {
else => 0o666,
};
pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
pub const OpenError = error{
SharingViolation,
PathAlreadyExists,
FileNotFound,
AccessDenied,
PipeBusy,
NameTooLong,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
Unexpected,
} || os.OpenError || os.FlockError;
pub const Lock = enum { None, Shared, Exclusive };

View File

@ -374,15 +374,13 @@ pub fn Watch(comptime V: type) type {
defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
const dir_handle = try windows.CreateFileW(
dirname_utf16le.ptr,
windows.FILE_LIST_DIRECTORY,
windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
null,
windows.OPEN_EXISTING,
windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
null,
);
const dir_handle = try windows.OpenFile(dirname_utf16le, .{
.dir = std.fs.cwd().fd,
.access_mask = windows.FILE_LIST_DIRECTORY,
.creation = windows.FILE_OPEN,
.io_mode = .blocking,
.open_dir = true,
});
var dir_handle_consumed = false;
defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);

View File

@ -56,9 +56,6 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy)
pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
switch (strat) {
.Shallow => {
// TODO detect via a trait when Key has no padding bits to
// hash it as an array of bytes.
// Otherwise, hash every element.
for (key) |element| {
hash(hasher, element, .Shallow);
}
@ -75,30 +72,34 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo
/// Strategy is provided to determine if pointers should be followed or not.
pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
const Key = @TypeOf(key);
if (strat == .Shallow and comptime meta.trait.hasUniqueRepresentation(Key)) {
@call(.{ .modifier = .always_inline }, hasher.update, .{mem.asBytes(&key)});
return;
}
switch (@typeInfo(Key)) {
.NoReturn,
.Opaque,
.Undefined,
.Void,
.Null,
.BoundFn,
.ComptimeFloat,
.ComptimeInt,
.Type,
.EnumLiteral,
.Frame,
.Float,
=> @compileError("cannot hash this type"),
// Help the optimizer see that hashing an int is easy by inlining!
// TODO Check if the situation is better after #561 is resolved.
.Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}),
.Float => |info| hash(hasher, @bitCast(std.meta.Int(false, info.bits), key), strat),
.Bool => hash(hasher, @boolToInt(key), strat),
.Enum => hash(hasher, @enumToInt(key), strat),
.ErrorSet => hash(hasher, @errorToInt(key), strat),
.AnyFrame, .Fn => hash(hasher, @ptrToInt(key), strat),
.AnyFrame, .BoundFn, .Fn => hash(hasher, @ptrToInt(key), strat),
.Pointer => @call(.{ .modifier = .always_inline }, hashPointer, .{ hasher, key, strat }),
@ -121,9 +122,6 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
},
.Struct => |info| {
// TODO detect via a trait when Key has no padding bits to
// hash it as an array of bytes.
// Otherwise, hash every field.
inline for (info.fields) |field| {
// We reuse the hash of the previous field as the seed for the
// next one so that they're dependant.
@ -266,12 +264,12 @@ test "hash slice deep" {
test "hash struct deep" {
const Foo = struct {
a: u32,
b: f64,
b: u16,
c: *bool,
const Self = @This();
pub fn init(allocator: *mem.Allocator, a_: u32, b_: f64, c_: bool) !Self {
pub fn init(allocator: *mem.Allocator, a_: u32, b_: u16, c_: bool) !Self {
const ptr = try allocator.create(bool);
ptr.* = c_;
return Self{ .a = a_, .b = b_, .c = ptr };
@ -279,9 +277,9 @@ test "hash struct deep" {
};
const allocator = std.testing.allocator;
const foo = try Foo.init(allocator, 123, 1.0, true);
const bar = try Foo.init(allocator, 123, 1.0, true);
const baz = try Foo.init(allocator, 123, 1.0, false);
const foo = try Foo.init(allocator, 123, 10, true);
const bar = try Foo.init(allocator, 123, 10, true);
const baz = try Foo.init(allocator, 123, 10, false);
defer allocator.destroy(foo.c);
defer allocator.destroy(bar.c);
defer allocator.destroy(baz.c);
@ -338,12 +336,12 @@ test "testHash struct" {
test "testHash union" {
const Foo = union(enum) {
A: u32,
B: f32,
B: bool,
C: u32,
};
const a = Foo{ .A = 18 };
var b = Foo{ .B = 12.34 };
var b = Foo{ .B = true };
const c = Foo{ .C = 18 };
testing.expect(testHash(a) == testHash(a));
testing.expect(testHash(a) != testHash(b));

View File

@ -5,6 +5,7 @@ const testing = std.testing;
const math = std.math;
const mem = std.mem;
const meta = std.meta;
const trait = meta.trait;
const autoHash = std.hash.autoHash;
const Wyhash = std.hash.Wyhash;
const Allocator = mem.Allocator;
@ -195,6 +196,10 @@ pub fn HashMap(
return self.unmanaged.getEntry(key);
}
pub fn getIndex(self: Self, key: K) ?usize {
return self.unmanaged.getIndex(key);
}
pub fn get(self: Self, key: K) ?V {
return self.unmanaged.get(key);
}
@ -478,17 +483,21 @@ pub fn HashMapUnmanaged(
}
pub fn getEntry(self: Self, key: K) ?*Entry {
const index = self.getIndex(key) orelse return null;
return &self.entries.items[index];
}
pub fn getIndex(self: Self, key: K) ?usize {
const header = self.index_header orelse {
// Linear scan.
const h = if (store_hash) hash(key) else {};
for (self.entries.items) |*item| {
for (self.entries.items) |*item, i| {
if (item.hash == h and eql(key, item.key)) {
return item;
return i;
}
}
return null;
};
switch (header.capacityIndexType()) {
.u8 => return self.getInternal(key, header, u8),
.u16 => return self.getInternal(key, header, u16),
@ -710,7 +719,7 @@ pub fn HashMapUnmanaged(
unreachable;
}
fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry {
fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?usize {
const indexes = header.indexes(I);
const h = hash(key);
const start_index = header.constrainIndex(h);
@ -724,7 +733,7 @@ pub fn HashMapUnmanaged(
const entry = &self.entries.items[index.entry_index];
const hash_match = if (store_hash) h == entry.hash else true;
if (hash_match and eql(key, entry.key))
return entry;
return index.entry_index;
}
return null;
}
@ -1023,9 +1032,13 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
return struct {
fn hash(key: K) u32 {
var hasher = Wyhash.init(0);
autoHash(&hasher, key);
return @truncate(u32, hasher.final());
if (comptime trait.hasUniqueRepresentation(K)) {
return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key)));
} else {
var hasher = Wyhash.init(0);
autoHash(&hasher, key);
return @truncate(u32, hasher.final());
}
}
}.hash;
}

View File

@ -99,7 +99,7 @@ pub const Mutable = struct {
pub fn toManaged(self: Mutable, allocator: *Allocator) Managed {
return .{
.allocator = allocator,
.limbs = limbs,
.limbs = self.limbs,
.metadata = if (self.positive)
self.len & ~Managed.sign_bit
else

View File

@ -2,6 +2,7 @@ const std = @import("../../std.zig");
const mem = std.mem;
const testing = std.testing;
const Managed = std.math.big.int.Managed;
const Mutable = std.math.big.int.Mutable;
const Limb = std.math.big.Limb;
const DoubleLimb = std.math.big.DoubleLimb;
const maxInt = std.math.maxInt;
@ -1453,3 +1454,24 @@ test "big.int gcd one large" {
testing.expect((try r.to(u64)) == 1);
}
test "big.int mutable to managed" {
const allocator = testing.allocator;
var limbs_buf = try allocator.alloc(Limb, 8);
defer allocator.free(limbs_buf);
var a = Mutable.init(limbs_buf, 0xdeadbeef);
var a_managed = a.toManaged(allocator);
testing.expect(a.toConst().eq(a_managed.toConst()));
}
test "big.int const to managed" {
var a = try Managed.initSet(testing.allocator, 123423453456);
defer a.deinit();
var b = try a.toConst().toManaged(testing.allocator);
defer b.deinit();
testing.expect(a.toConst().eq(b.toConst()));
}

View File

@ -2030,6 +2030,79 @@ test "rotate" {
testing.expect(eql(i32, &arr, &[_]i32{ 1, 2, 4, 5, 3 }));
}
/// Replace needle with replacement as many times as possible, writing to an output buffer which is assumed to be of
/// appropriate size. Use replacementSize to calculate an appropriate buffer size.
pub fn replace(comptime T: type, input: []const T, needle: []const T, replacement: []const T, output: []T) usize {
var i: usize = 0;
var slide: usize = 0;
var replacements: usize = 0;
while (slide < input.len) {
if (mem.indexOf(T, input[slide..], needle) == @as(usize, 0)) {
mem.copy(T, output[i..i + replacement.len], replacement);
i += replacement.len;
slide += needle.len;
replacements += 1;
} else {
output[i] = input[slide];
i += 1;
slide += 1;
}
}
return replacements;
}
test "replace" {
var output: [29]u8 = undefined;
var replacements = replace(u8, "All your base are belong to us", "base", "Zig", output[0..]);
testing.expect(replacements == 1);
testing.expect(eql(u8, output[0..], "All your Zig are belong to us"));
replacements = replace(u8, "Favor reading code over writing code.", "code", "", output[0..]);
testing.expect(replacements == 2);
testing.expect(eql(u8, output[0..], "Favor reading over writing ."));
}
/// Calculate the size needed in an output buffer to perform a replacement.
pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize {
var i: usize = 0;
var size: usize = input.len;
while (i < input.len) : (i += 1) {
if (mem.indexOf(T, input[i..], needle) == @as(usize, 0)) {
size = size - needle.len + replacement.len;
i += needle.len;
}
}
return size;
}
test "replacementSize" {
testing.expect(replacementSize(u8, "All your base are belong to us", "base", "Zig") == 29);
testing.expect(replacementSize(u8, "", "", "") == 0);
testing.expect(replacementSize(u8, "Favor reading code over writing code.", "code", "") == 29);
testing.expect(replacementSize(u8, "Only one obvious way to do things.", "things.", "things in Zig.") == 41);
}
/// Perform a replacement on an allocated buffer of pre-determined size. Caller must free returned memory.
pub fn replaceOwned(comptime T: type, allocator: *Allocator, input: []const T, needle: []const T, replacement: []const T) Allocator.Error![]T {
var output = try allocator.alloc(T, replacementSize(T, input, needle, replacement));
_ = replace(T, input, needle, replacement, output);
return output;
}
test "replaceOwned" {
const allocator = std.heap.page_allocator;
const base_replace = replaceOwned(u8, allocator, "All your base are belong to us", "base", "Zig") catch unreachable;
defer allocator.free(base_replace);
testing.expect(eql(u8, base_replace, "All your Zig are belong to us"));
const zen_replace = replaceOwned(u8, allocator, "Favor reading code over writing code.", " code", "") catch unreachable;
defer allocator.free(zen_replace);
testing.expect(eql(u8, zen_replace, "Favor reading over writing."));
}
/// Converts a little-endian integer to host endianness.
pub fn littleToNative(comptime T: type, x: T) T {
return switch (builtin.endian) {

View File

@ -429,3 +429,71 @@ test "std.meta.trait.hasFunctions" {
testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" }));
testing.expect(!hasFunctions(TestStruct2, tuple));
}
/// True if every value of the type `T` has a unique bit pattern representing it.
/// In other words, `T` has no unused bits and no padding.
pub fn hasUniqueRepresentation(comptime T: type) bool {
switch (@typeInfo(T)) {
else => return false, // TODO can we know if it's true for some of these types ?
.AnyFrame,
.Bool,
.BoundFn,
.Enum,
.ErrorSet,
.Fn,
.Int, // TODO check that it is still true
.Pointer,
=> return true,
.Array => |info| return comptime hasUniqueRepresentation(info.child),
.Struct => |info| {
var sum_size = @as(usize, 0);
inline for (info.fields) |field| {
const FieldType = field.field_type;
if (comptime !hasUniqueRepresentation(FieldType)) return false;
sum_size += @sizeOf(FieldType);
}
return @sizeOf(T) == sum_size;
},
.Vector => |info| return comptime hasUniqueRepresentation(info.child),
}
}
test "std.meta.trait.hasUniqueRepresentation" {
const TestStruct1 = struct {
a: u32,
b: u32,
};
testing.expect(hasUniqueRepresentation(TestStruct1));
const TestStruct2 = struct {
a: u32,
b: u16,
};
testing.expect(!hasUniqueRepresentation(TestStruct2));
const TestStruct3 = struct {
a: u32,
b: u32,
};
testing.expect(hasUniqueRepresentation(TestStruct3));
testing.expect(hasUniqueRepresentation(i1));
testing.expect(hasUniqueRepresentation(u2));
testing.expect(hasUniqueRepresentation(i3));
testing.expect(hasUniqueRepresentation(u4));
testing.expect(hasUniqueRepresentation(i5));
testing.expect(hasUniqueRepresentation(u6));
testing.expect(hasUniqueRepresentation(i7));
testing.expect(hasUniqueRepresentation(u8));
testing.expect(hasUniqueRepresentation(i9));
testing.expect(hasUniqueRepresentation(u10));
}

View File

@ -14,8 +14,8 @@ const has_unix_sockets = @hasDecl(os, "sockaddr_un");
pub const Address = extern union {
any: os.sockaddr,
in: os.sockaddr_in,
in6: os.sockaddr_in6,
in: Ip4Address,
in6: Ip6Address,
un: if (has_unix_sockets) os.sockaddr_un else void,
// TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512
@ -76,19 +76,227 @@ pub const Address = extern union {
}
}
pub fn parseIp6(buf: []const u8, port: u16) !Address {
return Address{.in6 = try Ip6Address.parse(buf, port) };
}
pub fn resolveIp6(buf: []const u8, port: u16) !Address {
return Address{.in6 = try Ip6Address.resolve(buf, port) };
}
pub fn parseIp4(buf: []const u8, port: u16) !Address {
return Address {.in = try Ip4Address.parse(buf, port) };
}
pub fn initIp4(addr: [4]u8, port: u16) Address {
return Address{.in = Ip4Address.init(addr, port) };
}
pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address {
return Address{.in6 = Ip6Address.init(addr, port, flowinfo, scope_id) };
}
pub fn initUnix(path: []const u8) !Address {
var sock_addr = os.sockaddr_un{
.family = os.AF_UNIX,
.path = undefined,
};
// this enables us to have the proper length of the socket in getOsSockLen
mem.set(u8, &sock_addr.path, 0);
if (path.len > sock_addr.path.len) return error.NameTooLong;
mem.copy(u8, &sock_addr.path, path);
return Address{ .un = sock_addr };
}
/// Returns the port in native endian.
/// Asserts that the address is ip4 or ip6.
pub fn getPort(self: Address) u16 {
return switch (self.any.family) {
os.AF_INET => self.in.getPort(),
os.AF_INET6 => self.in6.getPort(),
else => unreachable,
};
}
/// `port` is native-endian.
/// Asserts that the address is ip4 or ip6.
pub fn setPort(self: *Address, port: u16) void {
switch (self.any.family) {
os.AF_INET => self.in.setPort(port),
os.AF_INET6 => self.in6.setPort(port),
else => unreachable,
}
}
/// Asserts that `addr` is an IP address.
/// This function will read past the end of the pointer, with a size depending
/// on the address family.
pub fn initPosix(addr: *align(4) const os.sockaddr) Address {
switch (addr.family) {
os.AF_INET => return Address{ .in = Ip4Address{ .sa = @ptrCast(*const os.sockaddr_in, addr).*} },
os.AF_INET6 => return Address{ .in6 = Ip6Address{ .sa = @ptrCast(*const os.sockaddr_in6, addr).*} },
else => unreachable,
}
}
pub fn format(
self: Address,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: anytype,
) !void {
switch (self.any.family) {
os.AF_INET => try self.in.format(fmt, options, out_stream),
os.AF_INET6 => try self.in6.format(fmt, options, out_stream),
os.AF_UNIX => {
if (!has_unix_sockets) {
unreachable;
}
try std.fmt.format(out_stream, "{}", .{&self.un.path});
},
else => unreachable,
}
}
pub fn eql(a: Address, b: Address) bool {
const a_bytes = @ptrCast([*]const u8, &a.any)[0..a.getOsSockLen()];
const b_bytes = @ptrCast([*]const u8, &b.any)[0..b.getOsSockLen()];
return mem.eql(u8, a_bytes, b_bytes);
}
pub fn getOsSockLen(self: Address) os.socklen_t {
switch (self.any.family) {
os.AF_INET => return self.in.getOsSockLen(),
os.AF_INET6 => return self.in6.getOsSockLen(),
os.AF_UNIX => {
if (!has_unix_sockets) {
unreachable;
}
const path_len = std.mem.len(@ptrCast([*:0]const u8, &self.un.path));
return @intCast(os.socklen_t, @sizeOf(os.sockaddr_un) - self.un.path.len + path_len);
},
else => unreachable,
}
}
};
pub const Ip4Address = extern struct {
sa: os.sockaddr_in,
pub fn parse(buf: []const u8, port: u16) !Ip4Address {
var result = Ip4Address{
.sa = .{
.port = mem.nativeToBig(u16, port),
.addr = undefined,
}
};
const out_ptr = mem.sliceAsBytes(@as(*[1]u32, &result.sa.addr)[0..]);
var x: u8 = 0;
var index: u8 = 0;
var saw_any_digits = false;
for (buf) |c| {
if (c == '.') {
if (!saw_any_digits) {
return error.InvalidCharacter;
}
if (index == 3) {
return error.InvalidEnd;
}
out_ptr[index] = x;
index += 1;
x = 0;
saw_any_digits = false;
} else if (c >= '0' and c <= '9') {
saw_any_digits = true;
x = try std.math.mul(u8, x, 10);
x = try std.math.add(u8, x, c - '0');
} else {
return error.InvalidCharacter;
}
}
if (index == 3 and saw_any_digits) {
out_ptr[index] = x;
return result;
}
return error.Incomplete;
}
pub fn resolveIp(name: []const u8, port: u16) !Ip4Address {
if (parse(name, port)) |ip4| return ip4 else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
=> {},
}
return error.InvalidIPAddressFormat;
}
pub fn init(addr: [4]u8, port: u16) Ip4Address {
return Ip4Address {
.sa = os.sockaddr_in{
.port = mem.nativeToBig(u16, port),
.addr = @ptrCast(*align(1) const u32, &addr).*,
},
};
}
/// Returns the port in native endian.
/// Asserts that the address is ip4 or ip6.
pub fn getPort(self: Ip4Address) u16 {
return mem.bigToNative(u16, self.sa.port);
}
/// `port` is native-endian.
/// Asserts that the address is ip4 or ip6.
pub fn setPort(self: *Ip4Address, port: u16) void {
self.sa.port = mem.nativeToBig(u16, port);
}
pub fn format(
self: Ip4Address,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: anytype,
) !void {
const bytes = @ptrCast(*const [4]u8, &self.sa.addr);
try std.fmt.format(out_stream, "{}.{}.{}.{}:{}", .{
bytes[0],
bytes[1],
bytes[2],
bytes[3],
self.getPort(),
});
}
pub fn getOsSockLen(self: Ip4Address) os.socklen_t {
return @sizeOf(os.sockaddr_in);
}
};
pub const Ip6Address = extern struct {
sa: os.sockaddr_in6,
/// Parse a given IPv6 address string into an Address.
/// Assumes the Scope ID of the address is fully numeric.
/// For non-numeric addresses, see `resolveIp6`.
pub fn parseIp6(buf: []const u8, port: u16) !Address {
var result = Address{
.in6 = os.sockaddr_in6{
pub fn parse(buf: []const u8, port: u16) !Ip6Address {
var result = Ip6Address{
.sa = os.sockaddr_in6{
.scope_id = 0,
.port = mem.nativeToBig(u16, port),
.flowinfo = 0,
.addr = undefined,
},
};
var ip_slice = result.in6.addr[0..];
var ip_slice = result.sa.addr[0..];
var tail: [16]u8 = undefined;
@ -101,10 +309,10 @@ pub const Address = extern union {
if (scope_id) {
if (c >= '0' and c <= '9') {
const digit = c - '0';
if (@mulWithOverflow(u32, result.in6.scope_id, 10, &result.in6.scope_id)) {
if (@mulWithOverflow(u32, result.sa.scope_id, 10, &result.sa.scope_id)) {
return error.Overflow;
}
if (@addWithOverflow(u32, result.in6.scope_id, digit, &result.in6.scope_id)) {
if (@addWithOverflow(u32, result.sa.scope_id, digit, &result.sa.scope_id)) {
return error.Overflow;
}
} else {
@ -141,10 +349,10 @@ pub const Address = extern union {
return error.InvalidIpv4Mapping;
}
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
const addr = (parseIp4(buf[start_index..], 0) catch {
const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
}).in.addr;
ip_slice = result.in6.addr[0..];
}).sa.addr;
ip_slice = result.sa.addr[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
@ -180,22 +388,22 @@ pub const Address = extern union {
index += 1;
ip_slice[index] = @truncate(u8, x);
index += 1;
mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
mem.copy(u8, result.sa.addr[16 - index ..], ip_slice[0..index]);
return result;
}
}
pub fn resolveIp6(buf: []const u8, port: u16) !Address {
pub fn resolve(buf: []const u8, port: u16) !Ip6Address {
// TODO: Unify the implementations of resolveIp6 and parseIp6.
var result = Address{
.in6 = os.sockaddr_in6{
var result = Ip6Address{
.sa = os.sockaddr_in6{
.scope_id = 0,
.port = mem.nativeToBig(u16, port),
.flowinfo = 0,
.addr = undefined,
},
};
var ip_slice = result.in6.addr[0..];
var ip_slice = result.sa.addr[0..];
var tail: [16]u8 = undefined;
@ -256,10 +464,10 @@ pub const Address = extern union {
return error.InvalidIpv4Mapping;
}
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
const addr = (parseIp4(buf[start_index..], 0) catch {
const addr = (Ip4Address.parse(buf[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
}).in.addr;
ip_slice = result.in6.addr[0..];
}).sa.addr;
ip_slice = result.sa.addr[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
@ -299,7 +507,7 @@ pub const Address = extern union {
};
}
result.in6.scope_id = resolved_scope_id;
result.sa.scope_id = resolved_scope_id;
if (index == 14) {
ip_slice[14] = @truncate(u8, x >> 8);
@ -310,63 +518,14 @@ pub const Address = extern union {
index += 1;
ip_slice[index] = @truncate(u8, x);
index += 1;
mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
mem.copy(u8, result.sa.addr[16 - index ..], ip_slice[0..index]);
return result;
}
}
pub fn parseIp4(buf: []const u8, port: u16) !Address {
var result = Address{
.in = os.sockaddr_in{
.port = mem.nativeToBig(u16, port),
.addr = undefined,
},
};
const out_ptr = mem.sliceAsBytes(@as(*[1]u32, &result.in.addr)[0..]);
var x: u8 = 0;
var index: u8 = 0;
var saw_any_digits = false;
for (buf) |c| {
if (c == '.') {
if (!saw_any_digits) {
return error.InvalidCharacter;
}
if (index == 3) {
return error.InvalidEnd;
}
out_ptr[index] = x;
index += 1;
x = 0;
saw_any_digits = false;
} else if (c >= '0' and c <= '9') {
saw_any_digits = true;
x = try std.math.mul(u8, x, 10);
x = try std.math.add(u8, x, c - '0');
} else {
return error.InvalidCharacter;
}
}
if (index == 3 and saw_any_digits) {
out_ptr[index] = x;
return result;
}
return error.Incomplete;
}
pub fn initIp4(addr: [4]u8, port: u16) Address {
return Address{
.in = os.sockaddr_in{
.port = mem.nativeToBig(u16, port),
.addr = @ptrCast(*align(1) const u32, &addr).*,
},
};
}
pub fn initIp6(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Address {
return Address{
.in6 = os.sockaddr_in6{
pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address {
return Ip6Address{
.sa = os.sockaddr_in6{
.addr = addr,
.port = mem.nativeToBig(u16, port),
.flowinfo = flowinfo,
@ -375,147 +534,71 @@ pub const Address = extern union {
};
}
pub fn initUnix(path: []const u8) !Address {
var sock_addr = os.sockaddr_un{
.family = os.AF_UNIX,
.path = undefined,
};
// this enables us to have the proper length of the socket in getOsSockLen
mem.set(u8, &sock_addr.path, 0);
if (path.len > sock_addr.path.len) return error.NameTooLong;
mem.copy(u8, &sock_addr.path, path);
return Address{ .un = sock_addr };
}
/// Returns the port in native endian.
/// Asserts that the address is ip4 or ip6.
pub fn getPort(self: Address) u16 {
const big_endian_port = switch (self.any.family) {
os.AF_INET => self.in.port,
os.AF_INET6 => self.in6.port,
else => unreachable,
};
return mem.bigToNative(u16, big_endian_port);
pub fn getPort(self: Ip6Address) u16 {
return mem.bigToNative(u16, self.sa.port);
}
/// `port` is native-endian.
/// Asserts that the address is ip4 or ip6.
pub fn setPort(self: *Address, port: u16) void {
const ptr = switch (self.any.family) {
os.AF_INET => &self.in.port,
os.AF_INET6 => &self.in6.port,
else => unreachable,
};
ptr.* = mem.nativeToBig(u16, port);
}
/// Asserts that `addr` is an IP address.
/// This function will read past the end of the pointer, with a size depending
/// on the address family.
pub fn initPosix(addr: *align(4) const os.sockaddr) Address {
switch (addr.family) {
os.AF_INET => return Address{ .in = @ptrCast(*const os.sockaddr_in, addr).* },
os.AF_INET6 => return Address{ .in6 = @ptrCast(*const os.sockaddr_in6, addr).* },
else => unreachable,
}
pub fn setPort(self: *Ip6Address, port: u16) void {
self.sa.port = mem.nativeToBig(u16, port);
}
pub fn format(
self: Address,
self: Ip6Address,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: anytype,
) !void {
switch (self.any.family) {
os.AF_INET => {
const port = mem.bigToNative(u16, self.in.port);
const bytes = @ptrCast(*const [4]u8, &self.in.addr);
try std.fmt.format(out_stream, "{}.{}.{}.{}:{}", .{
bytes[0],
bytes[1],
bytes[2],
bytes[3],
port,
});
},
os.AF_INET6 => {
const port = mem.bigToNative(u16, self.in6.port);
if (mem.eql(u8, self.in6.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
try std.fmt.format(out_stream, "[::ffff:{}.{}.{}.{}]:{}", .{
self.in6.addr[12],
self.in6.addr[13],
self.in6.addr[14],
self.in6.addr[15],
port,
});
return;
}
const big_endian_parts = @ptrCast(*align(1) const [8]u16, &self.in6.addr);
const native_endian_parts = switch (builtin.endian) {
.Big => big_endian_parts.*,
.Little => blk: {
var buf: [8]u16 = undefined;
for (big_endian_parts) |part, i| {
buf[i] = mem.bigToNative(u16, part);
}
break :blk buf;
},
};
try out_stream.writeAll("[");
var i: usize = 0;
var abbrv = false;
while (i < native_endian_parts.len) : (i += 1) {
if (native_endian_parts[i] == 0) {
if (!abbrv) {
try out_stream.writeAll(if (i == 0) "::" else ":");
abbrv = true;
}
continue;
}
try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]});
if (i != native_endian_parts.len - 1) {
try out_stream.writeAll(":");
}
}
try std.fmt.format(out_stream, "]:{}", .{port});
},
os.AF_UNIX => {
if (!has_unix_sockets) {
unreachable;
}
try std.fmt.format(out_stream, "{}", .{&self.un.path});
},
else => unreachable,
const port = mem.bigToNative(u16, self.sa.port);
if (mem.eql(u8, self.sa.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
try std.fmt.format(out_stream, "[::ffff:{}.{}.{}.{}]:{}", .{
self.sa.addr[12],
self.sa.addr[13],
self.sa.addr[14],
self.sa.addr[15],
port,
});
return;
}
const big_endian_parts = @ptrCast(*align(1) const [8]u16, &self.sa.addr);
const native_endian_parts = switch (builtin.endian) {
.Big => big_endian_parts.*,
.Little => blk: {
var buf: [8]u16 = undefined;
for (big_endian_parts) |part, i| {
buf[i] = mem.bigToNative(u16, part);
}
break :blk buf;
},
};
try out_stream.writeAll("[");
var i: usize = 0;
var abbrv = false;
while (i < native_endian_parts.len) : (i += 1) {
if (native_endian_parts[i] == 0) {
if (!abbrv) {
try out_stream.writeAll(if (i == 0) "::" else ":");
abbrv = true;
}
continue;
}
try std.fmt.format(out_stream, "{x}", .{native_endian_parts[i]});
if (i != native_endian_parts.len - 1) {
try out_stream.writeAll(":");
}
}
try std.fmt.format(out_stream, "]:{}", .{port});
}
pub fn eql(a: Address, b: Address) bool {
const a_bytes = @ptrCast([*]const u8, &a.any)[0..a.getOsSockLen()];
const b_bytes = @ptrCast([*]const u8, &b.any)[0..b.getOsSockLen()];
return mem.eql(u8, a_bytes, b_bytes);
}
pub fn getOsSockLen(self: Address) os.socklen_t {
switch (self.any.family) {
os.AF_INET => return @sizeOf(os.sockaddr_in),
os.AF_INET6 => return @sizeOf(os.sockaddr_in6),
os.AF_UNIX => {
if (!has_unix_sockets) {
unreachable;
}
const path_len = std.mem.len(@ptrCast([*:0]const u8, &self.un.path));
return @intCast(os.socklen_t, @sizeOf(os.sockaddr_un) - self.un.path.len + path_len);
},
else => unreachable,
}
pub fn getOsSockLen(self: Ip6Address) os.socklen_t {
return @sizeOf(os.sockaddr_in6);
}
};
pub fn connectUnixSocket(path: []const u8) !fs.File {
const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
const sockfd = try os.socket(
@ -777,7 +860,7 @@ fn linuxLookupName(
@memset(@ptrCast([*]u8, &sa6), 0, @sizeOf(os.sockaddr_in6));
var da6 = os.sockaddr_in6{
.family = os.AF_INET6,
.scope_id = addr.addr.in6.scope_id,
.scope_id = addr.addr.in6.sa.scope_id,
.port = 65535,
.flowinfo = 0,
.addr = [1]u8{0} ** 16,
@ -795,7 +878,7 @@ fn linuxLookupName(
var salen: os.socklen_t = undefined;
var dalen: os.socklen_t = undefined;
if (addr.addr.any.family == os.AF_INET6) {
mem.copy(u8, &da6.addr, &addr.addr.in6.addr);
mem.copy(u8, &da6.addr, &addr.addr.in6.sa.addr);
da = @ptrCast(*os.sockaddr, &da6);
dalen = @sizeOf(os.sockaddr_in6);
sa = @ptrCast(*os.sockaddr, &sa6);
@ -803,8 +886,8 @@ fn linuxLookupName(
} else {
mem.copy(u8, &sa6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
mem.copy(u8, &da6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
mem.writeIntNative(u32, da6.addr[12..], addr.addr.in.addr);
da4.addr = addr.addr.in.addr;
mem.writeIntNative(u32, da6.addr[12..], addr.addr.in.sa.addr);
da4.addr = addr.addr.in.sa.addr;
da = @ptrCast(*os.sockaddr, &da4);
dalen = @sizeOf(os.sockaddr_in);
sa = @ptrCast(*os.sockaddr, &sa4);

View File

@ -1041,6 +1041,9 @@ pub const OpenError = error{
/// The underlying filesystem does not support file locks
FileLocksNotSupported,
BadPathName,
InvalidUtf8,
} || UnexpectedError;
/// Open and possibly create a file. Keeps trying if it gets interrupted.
@ -1092,18 +1095,65 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
}
}
fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
const w = windows;
var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
if (flags & O_RDWR != 0) {
access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
} else if (flags & O_WRONLY != 0) {
access_mask |= w.GENERIC_WRITE;
} else {
access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
}
const open_dir: bool = flags & O_DIRECTORY != 0;
const follow_symlinks: bool = flags & O_NOFOLLOW == 0;
const creation: w.ULONG = blk: {
if (flags & O_CREAT != 0) {
if (flags & O_EXCL != 0) {
break :blk w.FILE_CREATE;
}
}
break :blk w.FILE_OPEN;
};
return .{
.access_mask = access_mask,
.io_mode = .blocking,
.creation = creation,
.open_dir = open_dir,
.follow_symlinks = follow_symlinks,
};
}
/// Windows-only. The path parameter is
/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
/// Translates the POSIX open API call to a Windows API call.
pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t {
@compileError("TODO implement openW for windows");
/// TODO currently, this function does not handle all flag combinations
/// or makes use of perm argument.
pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
var options = openOptionsFromFlags(flags);
options.dir = std.fs.cwd().fd;
return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
error.WouldBlock => unreachable,
error.PipeBusy => unreachable,
else => |e| return e,
};
}
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openatC`.
/// TODO support windows
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
if (builtin.os.tag == .wasi) {
@compileError("use openatWasi instead");
}
if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return openatW(dir_fd, file_path_w.span(), flags, mode);
}
const file_path_c = try toPosixPath(file_path);
return openatZ(dir_fd, &file_path_c, flags, mode);
}
@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ");
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openat`.
/// TODO support windows
pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
return openatW(dir_fd, file_path_w.span(), flags, mode);
}
while (true) {
const rc = system.openat(dir_fd, file_path, flags, mode);
switch (errno(rc)) {
@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
}
}
/// Windows-only. Similar to `openat` but with pathname argument null-terminated
/// WTF16 encoded.
/// TODO currently, this function does not handle all flag combinations
/// or makes use of perm argument.
pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
var options = openOptionsFromFlags(flags);
options.dir = dir_fd;
return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
error.WouldBlock => unreachable,
error.PipeBusy => unreachable,
else => |e| return e,
};
}
pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
while (true) {
switch (errno(system.dup2(old_fd, new_fd))) {
@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
@compileError("unlink is not supported in WASI; use unlinkat instead");
} else if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return windows.DeleteFileW(file_path_w.span().ptr);
return unlinkW(file_path_w.span());
} else {
const file_path_c = try toPosixPath(file_path);
return unlinkZ(&file_path_c);
@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ");
pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
return windows.DeleteFileW(file_path_w.span().ptr);
return unlinkW(file_path_w.span());
}
switch (errno(system.unlink(file_path))) {
0 => return,
@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
}
}
/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd });
}
pub const UnlinkatError = UnlinkError || error{
/// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
DirNotEmpty,
@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return unlinkatW(dirfd, file_path_w.span().ptr, flags);
return unlinkatW(dirfd, file_path_w.span(), flags);
} else if (builtin.os.tag == .wasi) {
return unlinkatWasi(dirfd, file_path, flags);
} else {
@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
return unlinkatW(dirfd, file_path_w.span().ptr, flags);
return unlinkatW(dirfd, file_path_w.span(), flags);
}
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
0 => return,
@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
}
/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void {
const w = windows;
const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
const create_options_flags = if (want_rmdir_behavior)
@as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
else
@as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead?
const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
var nt_name = w.UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
// The Windows API makes this mutable, but it will not mutate here.
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
};
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
// Windows does not recognize this, but it does work with empty string.
nt_name.Length = 0;
}
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
// Can't remove the parent directory with an open handle.
return error.FileBusy;
}
var attr = w.OBJECT_ATTRIBUTES{
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: w.IO_STATUS_BLOCK = undefined;
var tmp_handle: w.HANDLE = undefined;
var rc = w.ntdll.NtCreateFile(
&tmp_handle,
w.SYNCHRONIZE | w.DELETE,
&attr,
&io,
null,
0,
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
w.FILE_OPEN,
create_options_flags,
null,
0,
);
if (rc == .SUCCESS) {
rc = w.ntdll.NtClose(tmp_handle);
}
switch (rc) {
.SUCCESS => return,
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.INVALID_PARAMETER => unreachable,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.NOT_A_DIRECTORY => return error.NotDir,
else => return w.unexpectedStatus(rc),
}
pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
const remove_dir = (flags & AT_REMOVEDIR) != 0;
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
}
const RenameError = error{
@ -2087,7 +2101,7 @@ pub fn renameatW(
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
} else if (builtin.os.tag == .wasi) {
return mkdiratWasi(dir_fd, sub_dir_path, mode);
} else {
@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
}
}
pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null);
pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
const sub_dir_handle = windows.OpenFile(sub_path_w, .{
.dir = dir_fd,
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
.creation = windows.FILE_CREATE,
.io_mode = .blocking,
.open_dir = true,
}) catch |err| switch (err) {
error.IsDir => unreachable,
error.PipeBusy => unreachable,
error.WouldBlock => unreachable,
else => |e| return e,
};
windows.CloseHandle(sub_dir_handle);
}
@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .wasi) {
@compileError("mkdir is not supported in WASI; use mkdirat instead");
} else if (builtin.os.tag == .windows) {
const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
windows.CloseHandle(sub_dir_handle);
return;
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
return mkdirW(dir_path_w.span(), mode);
} else {
const dir_path_c = try toPosixPath(dir_path);
return mkdirZ(&dir_path_c, mode);
@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null);
windows.CloseHandle(sub_dir_handle);
return;
return mkdirW(dir_path_w.span(), mode);
}
switch (errno(system.mkdir(dir_path, mode))) {
0 => return,
@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
}
}
/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.
pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
const sub_dir_handle = windows.OpenFile(dir_path_w, .{
.dir = std.fs.cwd().fd,
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
.creation = windows.FILE_CREATE,
.io_mode = .blocking,
.open_dir = true,
}) catch |err| switch (err) {
error.IsDir => unreachable,
error.PipeBusy => unreachable,
error.WouldBlock => unreachable,
else => |e| return e,
};
windows.CloseHandle(sub_dir_handle);
}
pub const DeleteDirError = error{
AccessDenied,
FileBusy,
@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
@compileError("rmdir is not supported in WASI; use unlinkat instead");
} else if (builtin.os.tag == .windows) {
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
return windows.RemoveDirectoryW(dir_path_w.span().ptr);
return rmdirW(dir_path_w.span());
} else {
const dir_path_c = try toPosixPath(dir_path);
return rmdirZ(&dir_path_c);
@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
return windows.RemoveDirectoryW(dir_path_w.span().ptr);
return rmdirW(dir_path_w.span());
}
switch (errno(system.rmdir(dir_path))) {
0 => return,
@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
}
}
/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
error.IsDir => unreachable,
else => |e| return e,
};
}
pub const ChangeCurDirError = error{
AccessDenied,
FileSystem,
@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .wasi) {
@compileError("readlink is not supported in WASI; use readlinkat instead");
} else if (builtin.os.tag == .windows) {
return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return readlinkW(file_path_w.span(), out_buffer);
} else {
const file_path_c = try toPosixPath(file_path);
return readlinkZ(&file_path_c, out_buffer);
@ -2363,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
/// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded.
/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.
/// See also `readlinkZ`.
pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer);
pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
}
/// Same as `readlink` except `file_path` is null-terminated.
pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
return readlinkW(file_path_w.span().ptr, out_buffer);
return readlinkW(file_path_w.span(), out_buffer);
}
const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
return readlinkatWasi(dirfd, file_path, out_buffer);
}
if (builtin.os.tag == .windows) {
return windows.ReadLink(dirfd, file_path, out_buffer);
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return readlinkatW(dirfd, file_path_w.span(), out_buffer);
}
const file_path_c = try toPosixPath(file_path);
return readlinkatZ(dirfd, &file_path_c, out_buffer);
@ -2429,8 +2478,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
/// See also `readlinkat`.
pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
return windows.ReadLinkW(dirfd, file_path, out_buffer);
pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
return windows.ReadLink(dirfd, file_path, out_buffer);
}
/// Same as `readlinkat` except `file_path` is null-terminated.
@ -2438,7 +2487,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea
pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
if (builtin.os.tag == .windows) {
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer);
return readlinkatW(dirfd, file_path_w.span(), out_buffer);
}
const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
switch (errno(rc)) {
@ -3959,7 +4008,7 @@ pub const RealPathError = error{
pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
return realpathW(pathname_w.span().ptr, out_buffer);
return realpathW(pathname_w.span(), out_buffer);
}
if (builtin.os.tag == .wasi) {
@compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
@ -3974,7 +4023,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ");
pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
if (builtin.os.tag == .windows) {
const pathname_w = try windows.cStrToPrefixedFileW(pathname);
return realpathW(pathname_w.span().ptr, out_buffer);
return realpathW(pathname_w.span(), out_buffer);
}
if (builtin.os.tag == .linux and !builtin.link_libc) {
const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) {
@ -4010,22 +4059,43 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
return mem.spanZ(result_path);
}
/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
/// TODO use ntdll for better semantics
pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
const h_file = try windows.CreateFileW(
pathname,
windows.GENERIC_READ,
windows.FILE_SHARE_READ,
null,
windows.OPEN_EXISTING,
windows.FILE_FLAG_BACKUP_SEMANTICS,
null,
);
defer windows.CloseHandle(h_file);
/// Same as `realpath` except `pathname` is UTF16LE-encoded.
/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
const w = windows;
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
const dir = std.fs.cwd().fd;
const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
const share_access = w.FILE_SHARE_READ;
const creation = w.FILE_OPEN;
const h_file = blk: {
const res = w.OpenFile(pathname, .{
.dir = dir,
.access_mask = access_mask,
.share_access = share_access,
.creation = creation,
.io_mode = .blocking,
}) catch |err| switch (err) {
error.IsDir => break :blk w.OpenFile(pathname, .{
.dir = dir,
.access_mask = access_mask,
.share_access = share_access,
.creation = creation,
.io_mode = .blocking,
.open_dir = true,
}) catch |er| switch (er) {
error.WouldBlock => unreachable,
else => |e2| return e2,
},
error.WouldBlock => unreachable,
else => |e| return e,
};
break :blk res;
};
defer w.CloseHandle(h_file);
var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS);
// Windows returns \\?\ prepended to the path.
// We strip it to make this function consistent across platforms.

View File

@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP;
pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;
pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;
pub const IPPROTO_RM = ws2_32.IPPROTO_RM;
pub const O_RDONLY = 0o0;
pub const O_WRONLY = 0o1;
pub const O_RDWR = 0o2;
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 = 0o200000;
pub const O_NOFOLLOW = 0o400000;
pub const O_CLOEXEC = 0o2000000;
pub const O_ASYNC = 0o20000;
pub const O_DIRECT = 0o40000;
pub const O_LARGEFILE = 0;
pub const O_NOATIME = 0o1000000;
pub const O_PATH = 0o10000000;
pub const O_TMPFILE = 0o20200000;
pub const O_NDELAY = O_NONBLOCK;

View File

@ -3,6 +3,7 @@ const os = std.os;
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
const expectError = testing.expectError;
const io = std.io;
const fs = std.fs;
const mem = std.mem;
@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir;
const Dir = std.fs.Dir;
const ArenaAllocator = std.heap.ArenaAllocator;
test "open smoke test" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
// TODO verify file attributes using `fstat`
var tmp = tmpDir(.{});
defer tmp.cleanup();
// Get base abs path
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const base_path = blk: {
const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
};
var file_path: []u8 = undefined;
var fd: os.fd_t = undefined;
const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
// Create some file using `open`.
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
fd = try os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
os.close(fd);
// Try this again with the same flags. This op should fail with error.PathAlreadyExists.
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
expectError(error.PathAlreadyExists, os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
// Try opening without `O_EXCL` flag.
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
fd = try os.open(file_path, os.O_RDWR | os.O_CREAT, mode);
os.close(fd);
// Try opening as a directory which should fail.
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
expectError(error.NotDir, os.open(file_path, os.O_RDWR | os.O_DIRECTORY, mode));
// Create some directory
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
try os.mkdir(file_path, mode);
// Open dir using `open`
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
fd = try os.open(file_path, os.O_RDONLY | os.O_DIRECTORY, mode);
os.close(fd);
// Try opening as file which should fail.
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
expectError(error.IsDir, os.open(file_path, os.O_RDWR, mode));
}
test "openat smoke test" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
// TODO verify file attributes using `fstatat`
var tmp = tmpDir(.{});
defer tmp.cleanup();
var fd: os.fd_t = undefined;
const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
// Create some file using `openat`.
fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
os.close(fd);
// Try this again with the same flags. This op should fail with error.PathAlreadyExists.
expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
// Try opening without `O_EXCL` flag.
fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT, mode);
os.close(fd);
// Try opening as a directory which should fail.
expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_DIRECTORY, mode));
// Create some directory
try os.mkdirat(tmp.dir.fd, "some_dir", mode);
// Open dir using `open`
fd = try os.openat(tmp.dir.fd, "some_dir", os.O_RDONLY | os.O_DIRECTORY, mode);
os.close(fd);
// Try opening as file which should fail.
expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O_RDWR, mode));
}
test "symlink with relative paths" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;
@ -27,7 +117,7 @@ test "symlink with relative paths" {
try cwd.writeFile("file.txt", "nonsense");
if (builtin.os.tag == .windows) {
try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false);
try os.windows.CreateSymbolicLink(cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
} else {
try os.symlink("file.txt", "symlinked");
}
@ -85,7 +175,7 @@ test "readlinkat" {
// create a symbolic link
if (builtin.os.tag == .windows) {
try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false);
try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
} else {
try os.symlinkat("file.txt", tmp.dir.fd, "link");
}

View File

@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig");
pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
pub const CreateFileError = error{
SharingViolation,
PathAlreadyExists,
/// When any of the path components can not be found or the file component can not
/// be found. Some operating systems distinguish between path components not found and
/// file components not found, but they are collapsed into FileNotFound to gain
/// consistency across operating systems.
FileNotFound,
AccessDenied,
PipeBusy,
NameTooLong,
/// On Windows, file paths must be valid Unicode.
InvalidUtf8,
/// On Windows, file paths cannot contain these characters:
/// '/', '*', '?', '"', '<', '>', '|'
BadPathName,
Unexpected,
};
pub fn CreateFile(
file_path: []const u8,
desired_access: DWORD,
share_mode: DWORD,
lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
creation_disposition: DWORD,
flags_and_attrs: DWORD,
hTemplateFile: ?HANDLE,
) CreateFileError!HANDLE {
const file_path_w = try sliceToPrefixedFileW(file_path);
return CreateFileW(file_path_w.span().ptr, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
}
pub fn CreateFileW(
file_path_w: [*:0]const u16,
desired_access: DWORD,
share_mode: DWORD,
lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
creation_disposition: DWORD,
flags_and_attrs: DWORD,
hTemplateFile: ?HANDLE,
) CreateFileError!HANDLE {
const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
if (result == INVALID_HANDLE_VALUE) {
switch (kernel32.GetLastError()) {
.SHARING_VIOLATION => return error.SharingViolation,
.ALREADY_EXISTS => return error.PathAlreadyExists,
.FILE_EXISTS => return error.PathAlreadyExists,
.FILE_NOT_FOUND => return error.FileNotFound,
.PATH_NOT_FOUND => return error.FileNotFound,
.ACCESS_DENIED => return error.AccessDenied,
.PIPE_BUSY => return error.PipeBusy,
.FILENAME_EXCED_RANGE => return error.NameTooLong,
else => |err| return unexpectedError(err),
}
}
return result;
}
pub const OpenError = error{
IsDir,
NotDir,
FileNotFound,
NoDevice,
SharingViolation,
AccessDenied,
PipeBusy,
PathAlreadyExists,
@ -111,15 +46,21 @@ pub const OpenFileOptions = struct {
share_access_nonblocking: bool = false,
creation: ULONG,
io_mode: std.io.ModeOverride,
/// If true, tries to open path as a directory.
/// Defaults to false.
open_dir: bool = false,
/// If false, tries to open path as a reparse point without dereferencing it.
/// Defaults to true.
follow_symlinks: bool = true,
};
/// TODO when share_access_nonblocking is false, this implementation uses
/// untinterruptible sleep() to block. This is not the final iteration of the API.
pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
if (mem.eql(u16, sub_path_w, &[_]u16{'.'})) {
if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir) {
return error.IsDir;
}
if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) {
if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) {
return error.IsDir;
}
@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE else FILE_NON_DIRECTORY_FILE;
// If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
var delay: usize = 1;
while (true) {
var flags: ULONG = undefined;
const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
const rc = ntdll.NtCreateFile(
&result,
options.access_mask,
@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
FILE_ATTRIBUTE_NORMAL,
options.share_access,
options.creation,
FILE_NON_DIRECTORY_FILE | blocking_flag,
flags,
null,
0,
);
@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.NOT_A_DIRECTORY => return error.NotDir,
else => return unexpectedStatus(rc),
}
}
@ -215,30 +159,61 @@ pub fn CreateEventExW(attributes: ?*SECURITY_ATTRIBUTES, nameW: [*:0]const u16,
}
}
pub const DeviceIoControlError = error{Unexpected};
/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
/// It implements similar behavior to `DeviceIoControl` and is meant to serve
/// as a direct substitute for that call.
/// TODO work out if we need to expose other arguments to the underlying syscalls.
pub fn DeviceIoControl(
h: HANDLE,
ioControlCode: DWORD,
ioControlCode: ULONG,
in: ?[]const u8,
out: ?[]u8,
overlapped: ?*OVERLAPPED,
) !DWORD {
var bytes: DWORD = undefined;
if (kernel32.DeviceIoControl(
h,
ioControlCode,
if (in) |i| i.ptr else null,
if (in) |i| @intCast(u32, i.len) else 0,
if (out) |o| o.ptr else null,
if (out) |o| @intCast(u32, o.len) else 0,
&bytes,
overlapped,
) == 0) {
switch (kernel32.GetLastError()) {
.IO_PENDING => if (overlapped == null) unreachable,
else => |err| return unexpectedError(err),
) DeviceIoControlError!void {
// Logic from: https://doxygen.reactos.org/d3/d74/deviceio_8c.html
const is_fsctl = (ioControlCode >> 16) == FILE_DEVICE_FILE_SYSTEM;
var io: IO_STATUS_BLOCK = undefined;
const in_ptr = if (in) |i| i.ptr else null;
const in_len = if (in) |i| @intCast(ULONG, i.len) else 0;
const out_ptr = if (out) |o| o.ptr else null;
const out_len = if (out) |o| @intCast(ULONG, o.len) else 0;
const rc = blk: {
if (is_fsctl) {
break :blk ntdll.NtFsControlFile(
h,
null,
null,
null,
&io,
ioControlCode,
in_ptr,
in_len,
out_ptr,
out_len,
);
} else {
break :blk ntdll.NtDeviceIoControlFile(
h,
null,
null,
null,
&io,
ioControlCode,
in_ptr,
in_len,
out_ptr,
out_len,
);
}
};
switch (rc) {
.SUCCESS => {},
.INVALID_PARAMETER => unreachable,
else => return unexpectedStatus(rc),
}
return bytes;
}
pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD {
@ -607,27 +582,14 @@ pub const CreateSymbolicLinkError = error{
PathAlreadyExists,
FileNotFound,
NameTooLong,
InvalidUtf8,
BadPathName,
NoDevice,
Unexpected,
};
pub fn CreateSymbolicLink(
dir: ?HANDLE,
sym_link_path: []const u8,
target_path: []const u8,
is_directory: bool,
) CreateSymbolicLinkError!void {
const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
const target_path_w = try sliceToPrefixedFileW(target_path);
return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
}
pub fn CreateSymbolicLinkW(
dir: ?HANDLE,
sym_link_path: [:0]const u16,
target_path: [:0]const u16,
sym_link_path: []const u16,
target_path: []const u16,
is_directory: bool,
) CreateSymbolicLinkError!void {
const SYMLINK_DATA = extern struct {
@ -641,71 +603,19 @@ pub fn CreateSymbolicLinkW(
Flags: ULONG,
};
var symlink_handle: HANDLE = undefined;
if (is_directory) {
const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) {
error.Overflow => return error.NameTooLong,
};
var nt_name = UNICODE_STRING{
.Length = sym_link_len_bytes,
.MaximumLength = sym_link_len_bytes,
.Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)),
};
if (sym_link_path[0] == '.' and sym_link_path[1] == 0) {
// Windows does not recognize this, but it does work with empty string.
nt_name.Length = 0;
}
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
const rc = ntdll.NtCreateFile(
&symlink_handle,
GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES,
&attr,
&io,
null,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_CREATE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
null,
0,
);
switch (rc) {
.SUCCESS => {},
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
.INVALID_PARAMETER => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
else => return unexpectedStatus(rc),
}
} else {
symlink_handle = OpenFile(sym_link_path, .{
.access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
.dir = dir,
.creation = FILE_CREATE,
.io_mode = .blocking,
}) catch |err| switch (err) {
error.WouldBlock => unreachable,
error.IsDir => return error.PathAlreadyExists,
error.PipeBusy => unreachable,
error.SharingViolation => return error.AccessDenied,
else => |e| return e,
};
}
const symlink_handle = OpenFile(sym_link_path, .{
.access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
.dir = dir,
.creation = FILE_CREATE,
.io_mode = .blocking,
.open_dir = is_directory,
}) catch |err| switch (err) {
error.IsDir => return error.PathAlreadyExists,
error.NotDir => unreachable,
error.WouldBlock => unreachable,
error.PipeBusy => unreachable,
else => |e| return e,
};
defer CloseHandle(symlink_handle);
// prepare reparse data buffer
@ -727,8 +637,7 @@ pub fn CreateSymbolicLinkW(
@memcpy(buffer[@sizeOf(SYMLINK_DATA)..], @ptrCast([*]const u8, target_path), target_path.len * 2);
const paths_start = @sizeOf(SYMLINK_DATA) + target_path.len * 2;
@memcpy(buffer[paths_start..].ptr, @ptrCast([*]const u8, target_path), target_path.len * 2);
// TODO replace with NtDeviceIoControl
_ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null);
_ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null);
}
pub const ReadLinkError = error{
@ -737,44 +646,32 @@ pub const ReadLinkError = error{
Unexpected,
NameTooLong,
UnsupportedReparsePointType,
InvalidUtf8,
BadPathName,
};
pub fn ReadLink(
dir: ?HANDLE,
sub_path: []const u8,
out_buffer: []u8,
) ReadLinkError![]u8 {
const sub_path_w = try sliceToPrefixedFileW(sub_path);
return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer);
}
pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
// Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.
// With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that
// failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible
// to open the symlink there and then.
const path_len_bytes = math.cast(u16, sub_path_w.len * 2) catch |err| switch (err) {
error.Overflow => return error.NameTooLong,
};
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
};
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
// Windows does not recognize this, but it does work with empty string.
nt_name.Length = 0;
}
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
var result_handle: HANDLE = undefined;
var io: IO_STATUS_BLOCK = undefined;
const rc = ntdll.NtCreateFile(
&result_handle,
FILE_READ_ATTRIBUTES,
@ -806,7 +703,7 @@ pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) Rea
defer CloseHandle(result_handle);
var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
_ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
_ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]);
const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0]));
switch (reparse_struct.ReparseTag) {
@ -848,24 +745,69 @@ pub const DeleteFileError = error{
NameTooLong,
FileBusy,
Unexpected,
NotDir,
IsDir,
};
pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
const filename_w = try sliceToPrefixedFileW(filename);
return DeleteFileW(filename_w.span().ptr);
}
pub const DeleteFileOptions = struct {
dir: ?HANDLE,
remove_dir: bool = false,
};
pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void {
if (kernel32.DeleteFileW(filename) == 0) {
switch (kernel32.GetLastError()) {
.FILE_NOT_FOUND => return error.FileNotFound,
.PATH_NOT_FOUND => return error.FileNotFound,
.ACCESS_DENIED => return error.AccessDenied,
.FILENAME_EXCED_RANGE => return error.NameTooLong,
.INVALID_PARAMETER => return error.NameTooLong,
.SHARING_VIOLATION => return error.FileBusy,
else => |err| return unexpectedError(err),
}
pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
const create_options_flags: ULONG = if (options.remove_dir)
FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
else
FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
// The Windows API makes this mutable, but it will not mutate here.
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
};
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
// Windows does not recognize this, but it does work with empty string.
nt_name.Length = 0;
}
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
// Can't remove the parent directory with an open handle.
return error.FileBusy;
}
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
var tmp_handle: HANDLE = undefined;
var rc = ntdll.NtCreateFile(
&tmp_handle,
SYNCHRONIZE | DELETE,
&attr,
&io,
null,
0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
create_options_flags,
null,
0,
);
switch (rc) {
.SUCCESS => return CloseHandle(tmp_handle),
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.INVALID_PARAMETER => unreachable,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.NOT_A_DIRECTORY => return error.NotDir,
else => return unexpectedStatus(rc),
}
}
@ -885,103 +827,6 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
}
}
pub const CreateDirectoryError = error{
NameTooLong,
PathAlreadyExists,
FileNotFound,
NoDevice,
AccessDenied,
InvalidUtf8,
BadPathName,
Unexpected,
};
/// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`.
pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE {
const pathname_w = try sliceToPrefixedFileW(pathname);
return CreateDirectoryW(dir, pathname_w.span().ptr, sa);
}
/// Same as `CreateDirectory` except takes a WTF-16 encoded path.
pub fn CreateDirectoryW(
dir: ?HANDLE,
sub_path_w: [*:0]const u16,
sa: ?*SECURITY_ATTRIBUTES,
) CreateDirectoryError!HANDLE {
const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
error.Overflow => return error.NameTooLong,
};
var nt_name = UNICODE_STRING{
.Length = path_len_bytes,
.MaximumLength = path_len_bytes,
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
};
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
// Windows does not recognize this, but it does work with empty string.
nt_name.Length = 0;
}
var attr = OBJECT_ATTRIBUTES{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
.ObjectName = &nt_name,
.SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
.SecurityQualityOfService = null,
};
var io: IO_STATUS_BLOCK = undefined;
var result_handle: HANDLE = undefined;
const rc = ntdll.NtCreateFile(
&result_handle,
GENERIC_READ | SYNCHRONIZE,
&attr,
&io,
null,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_CREATE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
null,
0,
);
switch (rc) {
.SUCCESS => return result_handle,
.OBJECT_NAME_INVALID => unreachable,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
.INVALID_PARAMETER => unreachable,
.ACCESS_DENIED => return error.AccessDenied,
.OBJECT_PATH_SYNTAX_BAD => unreachable,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
else => return unexpectedStatus(rc),
}
}
pub const RemoveDirectoryError = error{
FileNotFound,
DirNotEmpty,
Unexpected,
NotDir,
};
pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
const dir_path_w = try sliceToPrefixedFileW(dir_path);
return RemoveDirectoryW(dir_path_w.span().ptr);
}
pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
switch (kernel32.GetLastError()) {
.PATH_NOT_FOUND => return error.FileNotFound,
.DIR_NOT_EMPTY => return error.DirNotEmpty,
.DIRECTORY => return error.NotDir,
else => |err| return unexpectedError(err),
}
}
}
pub const GetStdHandleError = error{
NoStandardHandleAttached,
Unexpected,
@ -1463,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
}
/// Converts the path `s` to WTF16, null-terminated. If the path is absolute,
/// it will get NT-style prefix `\??\` prepended automatically. For prepending
/// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead.
/// it will get NT-style prefix `\??\` prepended automatically.
pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
// TODO https://github.com/ziglang/zig/issues/2765
var path_space: PathSpace = undefined;

View File

@ -54,6 +54,18 @@ pub extern "NtDll" fn NtDeviceIoControlFile(
OutputBuffer: ?PVOID,
OutputBufferLength: ULONG,
) callconv(.Stdcall) NTSTATUS;
pub extern "NtDll" fn NtFsControlFile(
FileHandle: HANDLE,
Event: ?HANDLE,
ApcRoutine: ?IO_APC_ROUTINE,
ApcContext: ?*c_void,
IoStatusBlock: *IO_STATUS_BLOCK,
FsControlCode: ULONG,
InputBuffer: ?*const c_void,
InputBufferLength: ULONG,
OutputBuffer: ?PVOID,
OutputBufferLength: ULONG,
) callconv(.Stdcall) NTSTATUS;
pub extern "NtDll" fn NtClose(Handle: HANDLE) callconv(.Stdcall) NTSTATUS;
pub extern "NtDll" fn RtlDosPathNameToNtPathName_U(
DosPathName: [*:0]const u16,

View File

@ -92,6 +92,7 @@ comptime {
@export(@import("compiler_rt/floatunsidf.zig").__floatunsidf, .{ .name = "__floatunsidf", .linkage = linkage });
@export(@import("compiler_rt/floatundidf.zig").__floatundidf, .{ .name = "__floatundidf", .linkage = linkage });
@export(@import("compiler_rt/floatditf.zig").__floatditf, .{ .name = "__floatditf", .linkage = linkage });
@export(@import("compiler_rt/floattitf.zig").__floattitf, .{ .name = "__floattitf", .linkage = linkage });
@export(@import("compiler_rt/floattidf.zig").__floattidf, .{ .name = "__floattidf", .linkage = linkage });
@export(@import("compiler_rt/floattisf.zig").__floattisf, .{ .name = "__floattisf", .linkage = linkage });

View File

@ -0,0 +1,38 @@
const builtin = @import("builtin");
const is_test = builtin.is_test;
const std = @import("std");
const maxInt = std.math.maxInt;
const significandBits = 112;
const exponentBias = 16383;
const implicitBit = (@as(u128, 1) << significandBits);
pub fn __floatditf(arg: i64) callconv(.C) f128 {
@setRuntimeSafety(is_test);
if (arg == 0)
return 0.0;
// All other cases begin by extracting the sign and absolute value of a
var sign: u128 = 0;
var aAbs = @bitCast(u64, arg);
if (arg < 0) {
sign = 1 << 127;
aAbs = ~@bitCast(u64, arg)+ 1;
}
// Exponent of (fp_t)a is the width of abs(a).
const exponent = 63 - @clz(u64, aAbs);
var result: u128 = undefined;
// Shift a into the significand field, rounding if it is a right-shift
const shift = significandBits - exponent;
result = @as(u128, aAbs) << shift ^ implicitBit;
result += (@as(u128, exponent) + exponentBias) << significandBits;
return @bitCast(f128, result | sign);
}
test "import floatditf" {
_ = @import("floatditf_test.zig");
}

View File

@ -0,0 +1,26 @@
const __floatditf = @import("floatditf.zig").__floatditf;
const testing = @import("std").testing;
fn test__floatditf(a: i64, expected: f128) void {
const x = __floatditf(a);
testing.expect(x == expected);
}
test "floatditf" {
test__floatditf(0x7fffffffffffffff, make_ti(0x403dffffffffffff, 0xfffc000000000000));
test__floatditf(0x123456789abcdef1, make_ti(0x403b23456789abcd, 0xef10000000000000));
test__floatditf(0x2, make_ti(0x4000000000000000, 0x0));
test__floatditf(0x1, make_ti(0x3fff000000000000, 0x0));
test__floatditf(0x0, make_ti(0x0, 0x0));
test__floatditf(@bitCast(i64, @as(u64, 0xffffffffffffffff)), make_ti(0xbfff000000000000, 0x0));
test__floatditf(@bitCast(i64, @as(u64, 0xfffffffffffffffe)), make_ti(0xc000000000000000, 0x0));
test__floatditf(-0x123456789abcdef1, make_ti(0xc03b23456789abcd, 0xef10000000000000));
test__floatditf(@bitCast(i64, @as(u64, 0x8000000000000000)), make_ti(0xc03e000000000000, 0x0));
}
fn make_ti(high: u64, low: u64) f128 {
var result: u128 = high;
result <<= 64;
result |= low;
return @bitCast(f128, result);
}

View File

@ -171,6 +171,59 @@ test "expectEqual.union(enum)" {
expectEqual(a10, a10);
}
/// This function is intended to be used only in tests. When the actual value is not
/// within the margin of the expected value,
/// prints diagnostics to stderr to show exactly how they are not equal, then aborts.
/// The types must be floating point
pub fn expectWithinMargin(expected: anytype, actual: @TypeOf(expected), margin: @TypeOf(expected)) void {
std.debug.assert(margin >= 0.0);
switch (@typeInfo(@TypeOf(actual))) {
.Float,
.ComptimeFloat,
=> {
if (@fabs(expected - actual) > margin) {
std.debug.panic("actual {}, not within margin {} of expected {}", .{ actual, margin, expected });
}
},
else => @compileError("Unable to compare non floating point values"),
}
}
test "expectWithinMargin.f32" {
const x: f32 = 12.0;
const y: f32 = 12.06;
expectWithinMargin(x, y, 0.1);
}
/// This function is intended to be used only in tests. When the actual value is not
/// within the epsilon of the expected value,
/// prints diagnostics to stderr to show exactly how they are not equal, then aborts.
/// The types must be floating point
pub fn expectWithinEpsilon(expected: anytype, actual: @TypeOf(expected), epsilon: @TypeOf(expected)) void {
std.debug.assert(epsilon >= 0.0 and epsilon <= 1.0);
const margin = epsilon * expected;
switch (@typeInfo(@TypeOf(actual))) {
.Float,
.ComptimeFloat,
=> {
if (@fabs(expected - actual) > margin) {
std.debug.panic("actual {}, not within epsilon {}, of expected {}", .{ actual, epsilon, expected });
}
},
else => @compileError("Unable to compare non floating point values"),
}
}
test "expectWithinEpsilon.f32" {
const x: f32 = 12.0;
const y: f32 = 13.2;
expectWithinEpsilon(x, y, 0.1);
}
/// This function is intended to be used only in tests. When the two slices are not
/// equal, prints diagnostics to stderr to show exactly how they are not equal,
/// then aborts.

View File

@ -43,6 +43,22 @@ pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usi
return .{ .line = line, .column = column };
}
pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
var line: isize = 0;
if (end >= start) {
for (source[start..end]) |byte| switch (byte) {
'\n' => line += 1,
else => continue,
};
} else {
for (source[end..start]) |byte| switch (byte) {
'\n' => line -= 1,
else => continue,
};
}
return line;
}
/// Returns the standard file system basename of a binary generated by the Zig compiler.
pub fn binNameAlloc(
allocator: *std.mem.Allocator,

View File

@ -1299,6 +1299,10 @@ pub const Node = struct {
});
}
pub fn body(self: *const FnProto) ?*Node {
return self.getTrailer("body_node");
}
pub fn getTrailer(self: *const FnProto, comptime name: []const u8) ?TrailerFlags.Field(name) {
const trailers_start = @alignCast(
@alignOf(ParamDecl),
@ -1381,7 +1385,7 @@ pub const Node = struct {
.Invalid => {},
}
if (self.getTrailer("body_node")) |body_node| {
if (self.body()) |body_node| {
if (i < 1) return body_node;
i -= 1;
}
@ -1397,7 +1401,7 @@ pub const Node = struct {
}
pub fn lastToken(self: *const FnProto) TokenIndex {
if (self.getTrailer("body_node")) |body_node| return body_node.lastToken();
if (self.body()) |body_node| return body_node.lastToken();
switch (self.return_type) {
.Explicit, .InferErrorSet => |node| return node.lastToken(),
.Invalid => |tok| return tok,

View File

@ -201,7 +201,16 @@ const Parser = struct {
p.findNextContainerMember();
const next = p.token_ids[p.tok_i];
switch (next) {
.Eof => break,
.Eof => {
// no invalid tokens were found
if (index == p.tok_i) break;
// Invalid tokens, add error and exit
try p.errors.append(p.gpa, .{
.ExpectedToken = .{ .token = index, .expected_id = .Comma },
});
break;
},
else => {
if (next == .RBrace) {
if (!top_level) break;

View File

@ -293,6 +293,14 @@ test "zig fmt: decl between fields" {
});
}
test "zig fmt: eof after missing comma" {
try testError(
\\foo()
, &[_]Error{
.ExpectedToken,
});
}
test "zig fmt: errdefer with payload" {
try testCanonical(
\\pub fn main() anyerror!void {

View File

@ -104,7 +104,6 @@ pub fn parse(
return error.InvalidCharacter;
},
},
else => unreachable,
}
}
unreachable;

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,11 @@
pub const Table = std.StringHashMap(*Package);
/// This should be used for file operations.
root_src_dir: std.fs.Dir,
/// Relative to `root_src_dir`.
root_src_path: []const u8,
/// This is for metadata purposes, for example putting into debug information.
root_src_dir_path: []u8,
/// Relative to `root_src_dir` and `root_src_dir_path`.
root_src_path: []u8,
table: Table,
/// No references to `root_src_dir` and `root_src_path` are kept.
@ -18,8 +21,11 @@ pub fn create(
errdefer allocator.destroy(ptr);
const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path);
errdefer allocator.free(root_src_path_dupe);
const root_src_dir_path = try mem.dupe(allocator, u8, root_src_dir);
errdefer allocator.free(root_src_dir_path);
ptr.* = .{
.root_src_dir = try base_dir.openDir(root_src_dir, .{}),
.root_src_dir_path = root_src_dir_path,
.root_src_path = root_src_path_dupe,
.table = Table.init(allocator),
};
@ -30,6 +36,7 @@ pub fn destroy(self: *Package) void {
const allocator = self.table.allocator;
self.root_src_dir.close();
allocator.free(self.root_src_path);
allocator.free(self.root_src_dir_path);
{
var it = self.table.iterator();
while (it.next()) |kv| {
@ -41,10 +48,9 @@ pub fn destroy(self: *Package) void {
}
pub fn add(self: *Package, name: []const u8, package: *Package) !void {
try self.table.ensureCapacity(self.table.items().len + 1);
const name_dupe = try mem.dupe(self.table.allocator, u8, name);
errdefer self.table.allocator.deinit(name_dupe);
const entry = try self.table.put(name_dupe, package);
assert(entry == null);
self.table.putAssumeCapacityNoClobber(name_dupe, package);
}
const std = @import("std");

View File

@ -17,6 +17,9 @@ pub const ResultLoc = union(enum) {
discard,
/// The expression has an inferred type, and it will be evaluated as an rvalue.
none,
/// The expression must generate a pointer rather than a value. For example, the left hand side
/// of an assignment uses an "LValue" result location.
lvalue,
/// The expression will be type coerced into this type, but it will be evaluated as an rvalue.
ty: *zir.Inst,
/// The expression must store its result into this typed pointer.
@ -33,7 +36,7 @@ pub const ResultLoc = union(enum) {
pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst {
const type_src = scope.tree().token_locs[type_node.firstToken()].start;
const type_type = try mod.addZIRInstConst(scope, type_src, .{
const type_type = try addZIRInstConst(mod, scope, type_src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_type),
});
@ -46,18 +49,45 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
switch (node.tag) {
.VarDecl => unreachable, // Handled in `blockExpr`.
.Assign => unreachable, // Handled in `blockExpr`.
.AssignBitAnd => unreachable, // Handled in `blockExpr`.
.AssignBitOr => unreachable, // Handled in `blockExpr`.
.AssignBitShiftLeft => unreachable, // Handled in `blockExpr`.
.AssignBitShiftRight => unreachable, // Handled in `blockExpr`.
.AssignBitXor => unreachable, // Handled in `blockExpr`.
.AssignDiv => unreachable, // Handled in `blockExpr`.
.AssignSub => unreachable, // Handled in `blockExpr`.
.AssignSubWrap => unreachable, // Handled in `blockExpr`.
.AssignMod => unreachable, // Handled in `blockExpr`.
.AssignAdd => unreachable, // Handled in `blockExpr`.
.AssignAddWrap => unreachable, // Handled in `blockExpr`.
.AssignMul => unreachable, // Handled in `blockExpr`.
.AssignMulWrap => unreachable, // Handled in `blockExpr`.
.Add => return arithmetic(mod, scope, rl, node.castTag(.Add).?, .add),
.Sub => return arithmetic(mod, scope, rl, node.castTag(.Sub).?, .sub),
.Add => return simpleBinOp(mod, scope, rl, node.castTag(.Add).?, .add),
.AddWrap => return simpleBinOp(mod, scope, rl, node.castTag(.AddWrap).?, .addwrap),
.Sub => return simpleBinOp(mod, scope, rl, node.castTag(.Sub).?, .sub),
.SubWrap => return simpleBinOp(mod, scope, rl, node.castTag(.SubWrap).?, .subwrap),
.Mul => return simpleBinOp(mod, scope, rl, node.castTag(.Mul).?, .mul),
.MulWrap => return simpleBinOp(mod, scope, rl, node.castTag(.MulWrap).?, .mulwrap),
.Div => return simpleBinOp(mod, scope, rl, node.castTag(.Div).?, .div),
.Mod => return simpleBinOp(mod, scope, rl, node.castTag(.Mod).?, .mod_rem),
.BitAnd => return simpleBinOp(mod, scope, rl, node.castTag(.BitAnd).?, .bitand),
.BitOr => return simpleBinOp(mod, scope, rl, node.castTag(.BitOr).?, .bitor),
.BitShiftLeft => return simpleBinOp(mod, scope, rl, node.castTag(.BitShiftLeft).?, .shl),
.BitShiftRight => return simpleBinOp(mod, scope, rl, node.castTag(.BitShiftRight).?, .shr),
.BitXor => return simpleBinOp(mod, scope, rl, node.castTag(.BitXor).?, .xor),
.BangEqual => return cmp(mod, scope, rl, node.castTag(.BangEqual).?, .cmp_neq),
.EqualEqual => return cmp(mod, scope, rl, node.castTag(.EqualEqual).?, .cmp_eq),
.GreaterThan => return cmp(mod, scope, rl, node.castTag(.GreaterThan).?, .cmp_gt),
.GreaterOrEqual => return cmp(mod, scope, rl, node.castTag(.GreaterOrEqual).?, .cmp_gte),
.LessThan => return cmp(mod, scope, rl, node.castTag(.LessThan).?, .cmp_lt),
.LessOrEqual => return cmp(mod, scope, rl, node.castTag(.LessOrEqual).?, .cmp_lte),
.BangEqual => return simpleBinOp(mod, scope, rl, node.castTag(.BangEqual).?, .cmp_neq),
.EqualEqual => return simpleBinOp(mod, scope, rl, node.castTag(.EqualEqual).?, .cmp_eq),
.GreaterThan => return simpleBinOp(mod, scope, rl, node.castTag(.GreaterThan).?, .cmp_gt),
.GreaterOrEqual => return simpleBinOp(mod, scope, rl, node.castTag(.GreaterOrEqual).?, .cmp_gte),
.LessThan => return simpleBinOp(mod, scope, rl, node.castTag(.LessThan).?, .cmp_lt),
.LessOrEqual => return simpleBinOp(mod, scope, rl, node.castTag(.LessOrEqual).?, .cmp_lte),
.Identifier => return rlWrap(mod, scope, rl, try identifier(mod, scope, node.castTag(.Identifier).?)),
.ArrayCat => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayCat).?, .array_cat),
.ArrayMult => return simpleBinOp(mod, scope, rl, node.castTag(.ArrayMult).?, .array_mul),
.Identifier => return try identifier(mod, scope, rl, node.castTag(.Identifier).?),
.Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)),
.StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)),
.IntegerLiteral => return rlWrap(mod, scope, rl, try integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?)),
@ -90,6 +120,8 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
var scope = parent_scope;
for (block_node.statements()) |statement| {
const src = scope.tree().token_locs[statement.firstToken()].start;
_ = try addZIRNoOp(mod, scope, src, .dbg_stmt);
switch (statement.tag) {
.VarDecl => {
const var_decl_node = statement.castTag(.VarDecl).?;
@ -99,10 +131,25 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
const ass = statement.castTag(.Assign).?;
try assign(mod, scope, ass);
},
.AssignBitAnd => try assignOp(mod, scope, statement.castTag(.AssignBitAnd).?, .bitand),
.AssignBitOr => try assignOp(mod, scope, statement.castTag(.AssignBitOr).?, .bitor),
.AssignBitShiftLeft => try assignOp(mod, scope, statement.castTag(.AssignBitShiftLeft).?, .shl),
.AssignBitShiftRight => try assignOp(mod, scope, statement.castTag(.AssignBitShiftRight).?, .shr),
.AssignBitXor => try assignOp(mod, scope, statement.castTag(.AssignBitXor).?, .xor),
.AssignDiv => try assignOp(mod, scope, statement.castTag(.AssignDiv).?, .div),
.AssignSub => try assignOp(mod, scope, statement.castTag(.AssignSub).?, .sub),
.AssignSubWrap => try assignOp(mod, scope, statement.castTag(.AssignSubWrap).?, .subwrap),
.AssignMod => try assignOp(mod, scope, statement.castTag(.AssignMod).?, .mod_rem),
.AssignAdd => try assignOp(mod, scope, statement.castTag(.AssignAdd).?, .add),
.AssignAddWrap => try assignOp(mod, scope, statement.castTag(.AssignAddWrap).?, .addwrap),
.AssignMul => try assignOp(mod, scope, statement.castTag(.AssignMul).?, .mul),
.AssignMulWrap => try assignOp(mod, scope, statement.castTag(.AssignMulWrap).?, .mulwrap),
else => {
const possibly_unused_result = try expr(mod, scope, .none, statement);
const src = scope.tree().token_locs[statement.firstToken()].start;
_ = try mod.addZIRUnOp(scope, src, .ensure_result_used, possibly_unused_result);
if (!possibly_unused_result.tag.isNoReturn()) {
_ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result);
}
},
}
}
@ -133,7 +180,7 @@ fn varDecl(
if (nodeMayNeedMemoryLocation(init_node)) {
if (node.getTrailer("type_node")) |type_node| {
const type_inst = try typeExpr(mod, scope, type_node);
const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst);
const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst);
const result_loc: ResultLoc = .{ .ptr = alloc };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalVal);
@ -145,7 +192,7 @@ fn varDecl(
};
return &sub_scope.base;
} else {
const alloc = try mod.addZIRNoOpT(scope, name_src, .alloc_inferred);
const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred);
const result_loc: ResultLoc = .{ .inferred_ptr = alloc };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalVal);
@ -176,7 +223,7 @@ fn varDecl(
.Keyword_var => {
if (node.getTrailer("type_node")) |type_node| {
const type_inst = try typeExpr(mod, scope, type_node);
const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst);
const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst);
const result_loc: ResultLoc = .{ .ptr = alloc };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalPtr);
@ -188,7 +235,7 @@ fn varDecl(
};
return &sub_scope.base;
} else {
const alloc = try mod.addZIRNoOp(scope, name_src, .alloc_inferred);
const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred);
const result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred).? };
const init_inst = try expr(mod, scope, result_loc, init_node);
const sub_scope = try block_arena.create(Scope.LocalPtr);
@ -207,28 +254,44 @@ fn varDecl(
fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!void {
if (infix_node.lhs.castTag(.Identifier)) |ident| {
const tree = scope.tree();
const ident_name = try identifierTokenString(mod, scope, ident.token);
// This intentionally does not support @"_" syntax.
const ident_name = scope.tree().tokenSlice(ident.token);
if (std.mem.eql(u8, ident_name, "_")) {
_ = try expr(mod, scope, .discard, infix_node.rhs);
return;
} else {
return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{});
}
} else {
return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{});
}
const lvalue = try expr(mod, scope, .lvalue, infix_node.lhs);
_ = try expr(mod, scope, .{ .ptr = lvalue }, infix_node.rhs);
}
fn assignOp(
mod: *Module,
scope: *Scope,
infix_node: *ast.Node.SimpleInfixOp,
op_inst_tag: zir.Inst.Tag,
) InnerError!void {
const lhs_ptr = try expr(mod, scope, .lvalue, infix_node.lhs);
const lhs = try addZIRUnOp(mod, scope, lhs_ptr.src, .deref, lhs_ptr);
const lhs_type = try addZIRUnOp(mod, scope, lhs_ptr.src, .typeof, lhs);
const rhs = try expr(mod, scope, .{ .ty = lhs_type }, infix_node.rhs);
const tree = scope.tree();
const src = tree.token_locs[infix_node.op_token].start;
const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs);
_ = try addZIRBinOp(mod, scope, src, .store, lhs_ptr, result);
}
fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[node.op_token].start;
const bool_type = try mod.addZIRInstConst(scope, src, .{
const bool_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.bool_type),
});
const operand = try expr(mod, scope, .{ .ty = bool_type }, node.rhs);
return mod.addZIRUnOp(scope, src, .boolnot, operand);
return addZIRUnOp(mod, scope, src, .boolnot, operand);
}
/// Identifier token -> String (allocated in scope.arena())
@ -257,7 +320,7 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToke
const ident_name = try identifierTokenString(mod, scope, node.token);
return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
}
fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
@ -268,47 +331,31 @@ fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!
const lhs = try expr(mod, scope, .none, node.lhs);
const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?);
const pointer = try mod.addZIRInst(scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{});
return mod.addZIRUnOp(scope, src, .deref, pointer);
const pointer = try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{});
return addZIRUnOp(mod, scope, src, .deref, pointer);
}
fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[node.rtoken].start;
const lhs = try expr(mod, scope, .none, node.lhs);
return mod.addZIRUnOp(scope, src, .deref, lhs);
return addZIRUnOp(mod, scope, src, .deref, lhs);
}
fn cmp(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
infix_node: *ast.Node.SimpleInfixOp,
cmp_inst_tag: zir.Inst.Tag,
) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[infix_node.op_token].start;
const lhs = try expr(mod, scope, .none, infix_node.lhs);
const rhs = try expr(mod, scope, .none, infix_node.rhs);
const result = try mod.addZIRBinOp(scope, src, cmp_inst_tag, lhs, rhs);
return rlWrap(mod, scope, rl, result);
}
fn arithmetic(
fn simpleBinOp(
mod: *Module,
scope: *Scope,
rl: ResultLoc,
infix_node: *ast.Node.SimpleInfixOp,
op_inst_tag: zir.Inst.Tag,
) InnerError!*zir.Inst {
const lhs = try expr(mod, scope, .none, infix_node.lhs);
const rhs = try expr(mod, scope, .none, infix_node.rhs);
const tree = scope.tree();
const src = tree.token_locs[infix_node.op_token].start;
const result = try mod.addZIRBinOp(scope, src, op_inst_tag, lhs, rhs);
const lhs = try expr(mod, scope, .none, infix_node.lhs);
const rhs = try expr(mod, scope, .none, infix_node.rhs);
const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs);
return rlWrap(mod, scope, rl, result);
}
@ -331,19 +378,19 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
const tree = scope.tree();
const if_src = tree.token_locs[if_node.if_token].start;
const bool_type = try mod.addZIRInstConst(scope, if_src, .{
const bool_type = try addZIRInstConst(mod, scope, if_src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.bool_type),
});
const cond = try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_node.condition);
const condbr = try mod.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{
const condbr = try addZIRInstSpecial(mod, &block_scope.base, if_src, zir.Inst.CondBr, .{
.condition = cond,
.then_body = undefined, // populated below
.else_body = undefined, // populated below
}, .{});
const block = try mod.addZIRInstBlock(scope, if_src, .{
const block = try addZIRInstBlock(mod, scope, if_src, .{
.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items),
});
var then_scope: Scope.GenZIR = .{
@ -359,14 +406,14 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
// proper type inference requires peer type resolution on the if's
// branches.
const branch_rl: ResultLoc = switch (rl) {
.discard, .none, .ty, .ptr => rl,
.discard, .none, .ty, .ptr, .lvalue => rl,
.inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block },
};
const then_result = try expr(mod, &then_scope.base, branch_rl, if_node.body);
if (!then_result.tag.isNoReturn()) {
const then_src = tree.token_locs[if_node.body.lastToken()].start;
_ = try mod.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{
_ = try addZIRInst(mod, &then_scope.base, then_src, zir.Inst.Break, .{
.block = block,
.operand = then_result,
}, .{});
@ -387,7 +434,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body);
if (!else_result.tag.isNoReturn()) {
const else_src = tree.token_locs[else_node.body.lastToken()].start;
_ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{
_ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.Break, .{
.block = block,
.operand = else_result,
}, .{});
@ -396,7 +443,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
// TODO Optimization opportunity: we can avoid an allocation and a memcpy here
// by directly allocating the body for this one instruction.
const else_src = tree.token_locs[if_node.lastToken()].start;
_ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{
_ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.BreakVoid, .{
.block = block,
}, .{});
}
@ -412,20 +459,20 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE
const src = tree.token_locs[cfe.ltoken].start;
if (cfe.getRHS()) |rhs_node| {
if (nodeMayNeedMemoryLocation(rhs_node)) {
const ret_ptr = try mod.addZIRNoOp(scope, src, .ret_ptr);
const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr);
const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node);
return mod.addZIRUnOp(scope, src, .@"return", operand);
return addZIRUnOp(mod, scope, src, .@"return", operand);
} else {
const fn_ret_ty = try mod.addZIRNoOp(scope, src, .ret_type);
const fn_ret_ty = try addZIRNoOp(mod, scope, src, .ret_type);
const operand = try expr(mod, scope, .{ .ty = fn_ret_ty }, rhs_node);
return mod.addZIRUnOp(scope, src, .@"return", operand);
return addZIRUnOp(mod, scope, src, .@"return", operand);
}
} else {
return mod.addZIRNoOp(scope, src, .returnvoid);
return addZIRNoOp(mod, scope, src, .returnvoid);
}
}
fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError!*zir.Inst {
fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneToken) InnerError!*zir.Inst {
const tracy = trace(@src());
defer tracy.end();
@ -437,7 +484,8 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
}
if (getSimplePrimitiveValue(ident_name)) |typed_value| {
return mod.addZIRInstConst(scope, src, typed_value);
const result = try addZIRInstConst(mod, scope, src, typed_value);
return rlWrap(mod, scope, rl, result);
}
if (ident_name.len >= 2) integer: {
@ -461,16 +509,18 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
else => {
const int_type_payload = try scope.arena().create(Value.Payload.IntType);
int_type_payload.* = .{ .signed = is_signed, .bits = bit_count };
return mod.addZIRInstConst(scope, src, .{
const result = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.comptime_int),
.val = Value.initPayload(&int_type_payload.base),
});
return rlWrap(mod, scope, rl, result);
},
};
return mod.addZIRInstConst(scope, src, .{
const result = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = val,
});
return rlWrap(mod, scope, rl, result);
}
}
@ -481,14 +531,19 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
.local_val => {
const local_val = s.cast(Scope.LocalVal).?;
if (mem.eql(u8, local_val.name, ident_name)) {
return local_val.inst;
return rlWrap(mod, scope, rl, local_val.inst);
}
s = local_val.parent;
},
.local_ptr => {
const local_ptr = s.cast(Scope.LocalPtr).?;
if (mem.eql(u8, local_ptr.name, ident_name)) {
return try mod.addZIRUnOp(scope, src, .deref, local_ptr.ptr);
if (rl == .lvalue) {
return local_ptr.ptr;
} else {
const result = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr);
return rlWrap(mod, scope, rl, result);
}
}
s = local_ptr.parent;
},
@ -498,7 +553,9 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError
}
if (mod.lookupDeclName(scope, ident_name)) |decl| {
return try mod.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{});
// TODO handle lvalues
const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{});
return rlWrap(mod, scope, rl, result);
}
return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name});
@ -520,7 +577,7 @@ fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.OneToken) Inner
};
const src = tree.token_locs[str_lit.token].start;
return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{});
return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{});
}
fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) InnerError!*zir.Inst {
@ -545,7 +602,7 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) Inne
const int_payload = try arena.create(Value.Payload.Int_u64);
int_payload.* = .{ .int = small_int };
const src = tree.token_locs[int_lit.token].start;
return mod.addZIRInstConst(scope, src, .{
return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.comptime_int),
.val = Value.initPayload(&int_payload.base),
});
@ -568,7 +625,7 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) Inne
const float_payload = try arena.create(Value.Payload.Float_128);
float_payload.* = .{ .val = val };
const src = tree.token_locs[float_lit.token].start;
return mod.addZIRInstConst(scope, src, .{
return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.comptime_float),
.val = Value.initPayload(&float_payload.base),
});
@ -578,7 +635,7 @@ fn undefLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerErro
const arena = scope.arena();
const tree = scope.tree();
const src = tree.token_locs[node.token].start;
return mod.addZIRInstConst(scope, src, .{
return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.@"undefined"),
.val = Value.initTag(.undef),
});
@ -588,7 +645,7 @@ fn boolLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError
const arena = scope.arena();
const tree = scope.tree();
const src = tree.token_locs[node.token].start;
return mod.addZIRInstConst(scope, src, .{
return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.bool),
.val = switch (tree.token_ids[node.token]) {
.Keyword_true => Value.initTag(.bool_true),
@ -602,7 +659,7 @@ fn nullLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError
const arena = scope.arena();
const tree = scope.tree();
const src = tree.token_locs[node.token].start;
return mod.addZIRInstConst(scope, src, .{
return addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.@"null"),
.val = Value.initTag(.null_value),
});
@ -620,7 +677,7 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi
const src = tree.token_locs[asm_node.asm_token].start;
const str_type = try mod.addZIRInstConst(scope, src, .{
const str_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.const_slice_u8_type),
});
@ -632,11 +689,11 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi
args[i] = try expr(mod, scope, .none, input.expr);
}
const return_type = try mod.addZIRInstConst(scope, src, .{
const return_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.void_type),
});
const asm_inst = try mod.addZIRInst(scope, src, zir.Inst.Asm, .{
const asm_inst = try addZIRInst(mod, scope, src, zir.Inst.Asm, .{
.asm_source = try expr(mod, scope, str_type_rl, asm_node.template),
.return_type = return_type,
}, .{
@ -666,14 +723,14 @@ fn simpleCast(
try ensureBuiltinParamCount(mod, scope, call, 2);
const tree = scope.tree();
const src = tree.token_locs[call.builtin_token].start;
const type_type = try mod.addZIRInstConst(scope, src, .{
const type_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_type),
});
const params = call.params();
const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]);
const rhs = try expr(mod, scope, .none, params[1]);
const result = try mod.addZIRBinOp(scope, src, inst_tag, dest_type, rhs);
const result = try addZIRBinOp(mod, scope, src, inst_tag, dest_type, rhs);
return rlWrap(mod, scope, rl, result);
}
@ -682,7 +739,7 @@ fn ptrToInt(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError
const operand = try expr(mod, scope, .none, call.params()[0]);
const tree = scope.tree();
const src = tree.token_locs[call.builtin_token].start;
return mod.addZIRUnOp(scope, src, .ptrtoint, operand);
return addZIRUnOp(mod, scope, src, .ptrtoint, operand);
}
fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst {
@ -695,15 +752,19 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I
.none => return try expr(mod, scope, .{ .ty = dest_type }, params[1]),
.discard => {
const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]);
_ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result);
_ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
return result;
},
.lvalue => {
const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]);
return addZIRUnOp(mod, scope, result.src, .ref, result);
},
.ty => |result_ty| {
const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]);
return mod.addZIRBinOp(scope, src, .as, result_ty, result);
return addZIRBinOp(mod, scope, src, .as, result_ty, result);
},
.ptr => |result_ptr| {
const casted_result_ptr = try mod.addZIRBinOp(scope, src, .coerce_result_ptr, dest_type, result_ptr);
const casted_result_ptr = try addZIRBinOp(mod, scope, src, .coerce_result_ptr, dest_type, result_ptr);
return expr(mod, scope, .{ .ptr = casted_result_ptr }, params[1]);
},
.bitcasted_ptr => |bitcasted_ptr| {
@ -715,7 +776,7 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I
return mod.failTok(scope, call.builtin_token, "TODO implement @as with inferred-type result location pointer", .{});
},
.block_ptr => |block_ptr| {
const casted_block_ptr = try mod.addZIRInst(scope, src, zir.Inst.CoerceResultBlockPtr, .{
const casted_block_ptr = try addZIRInst(mod, scope, src, zir.Inst.CoerceResultBlockPtr, .{
.dest_type = dest_type,
.block = block_ptr,
}, .{});
@ -728,7 +789,7 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa
try ensureBuiltinParamCount(mod, scope, call, 2);
const tree = scope.tree();
const src = tree.token_locs[call.builtin_token].start;
const type_type = try mod.addZIRInstConst(scope, src, .{
const type_type = try addZIRInstConst(mod, scope, src, .{
.ty = Type.initTag(.type),
.val = Value.initTag(.type_type),
});
@ -737,21 +798,26 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa
switch (rl) {
.none => {
const operand = try expr(mod, scope, .none, params[1]);
return mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand);
return addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand);
},
.discard => {
const operand = try expr(mod, scope, .none, params[1]);
const result = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand);
_ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result);
const result = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand);
_ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
return result;
},
.lvalue => {
const operand = try expr(mod, scope, .lvalue, params[1]);
const result = try addZIRBinOp(mod, scope, src, .bitcast_lvalue, dest_type, operand);
return result;
},
.ty => |result_ty| {
const result = try expr(mod, scope, .none, params[1]);
const bitcasted = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, result);
return mod.addZIRBinOp(scope, src, .as, result_ty, bitcasted);
const bitcasted = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, result);
return addZIRBinOp(mod, scope, src, .as, result_ty, bitcasted);
},
.ptr => |result_ptr| {
const casted_result_ptr = try mod.addZIRUnOp(scope, src, .bitcast_result_ptr, result_ptr);
const casted_result_ptr = try addZIRUnOp(mod, scope, src, .bitcast_result_ptr, result_ptr);
return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, params[1]);
},
.bitcasted_ptr => |bitcasted_ptr| {
@ -799,7 +865,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In
const args = try scope.getGenZIR().arena.alloc(*zir.Inst, param_nodes.len);
for (param_nodes) |param_node, i| {
const param_src = tree.token_locs[param_node.firstToken()].start;
const param_type = try mod.addZIRInst(scope, param_src, zir.Inst.ParamType, .{
const param_type = try addZIRInst(mod, scope, param_src, zir.Inst.ParamType, .{
.func = lhs,
.arg_index = i,
}, .{});
@ -807,7 +873,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In
}
const src = tree.token_locs[node.lhs.firstToken()].start;
const result = try mod.addZIRInst(scope, src, zir.Inst.Call, .{
const result = try addZIRInst(mod, scope, src, zir.Inst.Call, .{
.func = lhs,
.args = args,
}, .{});
@ -818,7 +884,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In
fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.OneToken) InnerError!*zir.Inst {
const tree = scope.tree();
const src = tree.token_locs[unreach_node.token].start;
return mod.addZIRNoOp(scope, src, .@"unreachable");
return addZIRNoOp(mod, scope, src, .@"unreachable");
}
fn getSimplePrimitiveValue(name: []const u8) ?TypedValue {
@ -1000,19 +1066,20 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr
.none => return result,
.discard => {
// Emit a compile error for discarding error values.
_ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result);
_ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result);
return result;
},
.ty => |ty_inst| return mod.addZIRBinOp(scope, result.src, .as, ty_inst, result),
.lvalue => {
// We need a pointer but we have a value.
return addZIRUnOp(mod, scope, result.src, .ref, result);
},
.ty => |ty_inst| return addZIRBinOp(mod, scope, result.src, .as, ty_inst, result),
.ptr => |ptr_inst| {
const casted_result = try mod.addZIRInst(scope, result.src, zir.Inst.CoerceToPtrElem, .{
const casted_result = try addZIRInst(mod, scope, result.src, zir.Inst.CoerceToPtrElem, .{
.ptr = ptr_inst,
.value = result,
}, .{});
_ = try mod.addZIRInst(scope, result.src, zir.Inst.Store, .{
.ptr = ptr_inst,
.value = casted_result,
}, .{});
_ = try addZIRBinOp(mod, scope, result.src, .store, ptr_inst, casted_result);
return casted_result;
},
.bitcasted_ptr => |bitcasted_ptr| {
@ -1026,3 +1093,121 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr
},
}
}
pub fn addZIRInstSpecial(
mod: *Module,
scope: *Scope,
src: usize,
comptime T: type,
positionals: std.meta.fieldInfo(T, "positionals").field_type,
kw_args: std.meta.fieldInfo(T, "kw_args").field_type,
) !*T {
const gen_zir = scope.getGenZIR();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(T);
inst.* = .{
.base = .{
.tag = T.base_tag,
.src = src,
},
.positionals = positionals,
.kw_args = kw_args,
};
gen_zir.instructions.appendAssumeCapacity(&inst.base);
return inst;
}
pub fn addZIRNoOpT(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp {
const gen_zir = scope.getGenZIR();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(zir.Inst.NoOp);
inst.* = .{
.base = .{
.tag = tag,
.src = src,
},
.positionals = .{},
.kw_args = .{},
};
gen_zir.instructions.appendAssumeCapacity(&inst.base);
return inst;
}
pub fn addZIRNoOp(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst {
const inst = try addZIRNoOpT(mod, scope, src, tag);
return &inst.base;
}
pub fn addZIRUnOp(
mod: *Module,
scope: *Scope,
src: usize,
tag: zir.Inst.Tag,
operand: *zir.Inst,
) !*zir.Inst {
const gen_zir = scope.getGenZIR();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(zir.Inst.UnOp);
inst.* = .{
.base = .{
.tag = tag,
.src = src,
},
.positionals = .{
.operand = operand,
},
.kw_args = .{},
};
gen_zir.instructions.appendAssumeCapacity(&inst.base);
return &inst.base;
}
pub fn addZIRBinOp(
mod: *Module,
scope: *Scope,
src: usize,
tag: zir.Inst.Tag,
lhs: *zir.Inst,
rhs: *zir.Inst,
) !*zir.Inst {
const gen_zir = scope.getGenZIR();
try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
const inst = try gen_zir.arena.create(zir.Inst.BinOp);
inst.* = .{
.base = .{
.tag = tag,
.src = src,
},
.positionals = .{
.lhs = lhs,
.rhs = rhs,
},
.kw_args = .{},
};
gen_zir.instructions.appendAssumeCapacity(&inst.base);
return &inst.base;
}
pub fn addZIRInst(
mod: *Module,
scope: *Scope,
src: usize,
comptime T: type,
positionals: std.meta.fieldInfo(T, "positionals").field_type,
kw_args: std.meta.fieldInfo(T, "kw_args").field_type,
) !*zir.Inst {
const inst_special = try addZIRInstSpecial(mod, scope, src, T, positionals, kw_args);
return &inst_special.base;
}
/// TODO The existence of this function is a workaround for a bug in stage1.
pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst {
const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type;
return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{});
}
/// TODO The existence of this function is a workaround for a bug in stage1.
pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block {
const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type;
return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{});
}

View File

@ -828,6 +828,14 @@ pub const ZigClangExpr_ConstExprUsage = extern enum {
EvaluateForMangling,
};
pub const ZigClangUnaryExprOrTypeTrait_Kind = extern enum {
SizeOf,
AlignOf,
VecStep,
OpenMPRequiredSimdAlign,
PreferredAlignOf,
};
pub extern fn ZigClangSourceManager_getSpellingLoc(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) struct_ZigClangSourceLocation;
pub extern fn ZigClangSourceManager_getFilename(self: *const struct_ZigClangSourceManager, SpellingLoc: struct_ZigClangSourceLocation) ?[*:0]const u8;
pub extern fn ZigClangSourceManager_getSpellingLineNumber(self: ?*const struct_ZigClangSourceManager, Loc: struct_ZigClangSourceLocation) c_uint;
@ -1225,6 +1233,7 @@ pub extern fn ZigClangCallExpr_getArgs(*const ZigClangCallExpr) [*]const *const
pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangQualType;
pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangSourceLocation;
pub extern fn ZigClangUnaryExprOrTypeTraitExpr_getKind(*const ZigClangUnaryExprOrTypeTraitExpr) ZigClangUnaryExprOrTypeTrait_Kind;
pub extern fn ZigClangUnaryOperator_getOpcode(*const ZigClangUnaryOperator) ZigClangUO;
pub extern fn ZigClangUnaryOperator_getType(*const ZigClangUnaryOperator) ZigClangQualType;

File diff suppressed because it is too large Load Diff

View File

@ -89,17 +89,17 @@ fn genFn(file: *C, decl: *Decl) !void {
const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
const instructions = func.analysis.success.instructions;
if (instructions.len > 0) {
try writer.writeAll("\n");
for (instructions) |inst| {
try writer.writeAll("\n ");
switch (inst.tag) {
.assembly => try genAsm(file, inst.castTag(.assembly).?, decl),
.call => try genCall(file, inst.castTag(.call).?, decl),
.ret => try genRet(file, inst.castTag(.ret).?, decl, tv.ty.fnReturnType()),
.retvoid => try file.main.writer().print("return;", .{}),
.retvoid => try file.main.writer().print(" return;\n", .{}),
.dbg_stmt => try genDbgStmt(file, inst.castTag(.dbg_stmt).?, decl),
else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
}
}
try writer.writeAll("\n");
}
try writer.writeAll("}\n\n");
@ -112,6 +112,7 @@ fn genRet(file: *C, inst: *Inst.UnOp, decl: *Decl, expected_return_type: Type) !
fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
const writer = file.main.writer();
const header = file.header.writer();
try writer.writeAll(" ");
if (inst.func.castTag(.constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const target = func_val.func.owner_decl;
@ -126,7 +127,7 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
try renderFunctionSignature(file, header, target);
try header.writeAll(";\n");
}
try writer.print("{}();", .{tname});
try writer.print("{}();\n", .{tname});
} else {
return file.fail(decl.src(), "TODO non-function call target?", .{});
}
@ -138,8 +139,13 @@ fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void {
}
}
fn genDbgStmt(file: *C, inst: *Inst.NoOp, decl: *Decl) !void {
// TODO emit #line directive here with line number and filename
}
fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void {
const writer = file.main.writer();
try writer.writeAll(" ");
for (as.inputs) |i, index| {
if (i[0] == '{' and i[i.len - 1] == '}') {
const reg = i[1 .. i.len - 1];
@ -187,5 +193,5 @@ fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void {
}
}
}
try writer.writeAll(");");
try writer.writeAll(");\n");
}

View File

@ -0,0 +1,92 @@
const std = @import("std");
pub const instructions = struct {
pub const CallBreak = packed struct {
pub const Mode = packed enum(u12) { ecall, ebreak };
opcode: u7 = 0b1110011,
unused1: u5 = 0,
unused2: u3 = 0,
unused3: u5 = 0,
mode: u12, //: Mode
};
// I-type
pub const Addi = packed struct {
pub const Mode = packed enum(u3) { addi = 0b000, slti = 0b010, sltiu = 0b011, xori = 0b100, ori = 0b110, andi = 0b111 };
opcode: u7 = 0b0010011,
rd: u5,
mode: u3, //: Mode
rs1: u5,
imm: i12,
};
pub const Lui = packed struct {
opcode: u7 = 0b0110111,
rd: u5,
imm: i20,
};
// I_type
pub const Load = packed struct {
pub const Mode = packed enum(u3) { ld = 0b011, lwu = 0b110 };
opcode: u7 = 0b0000011,
rd: u5,
mode: u3, //: Mode
rs1: u5,
offset: i12,
};
// I-type
pub const Jalr = packed struct {
opcode: u7 = 0b1100111,
rd: u5,
mode: u3 = 0,
rs1: u5,
offset: i12,
};
};
// zig fmt: off
pub const RawRegister = enum(u8) {
x0, x1, x2, x3, x4, x5, x6, x7,
x8, x9, x10, x11, x12, x13, x14, x15,
x16, x17, x18, x19, x20, x21, x22, x23,
x24, x25, x26, x27, x28, x29, x30, x31,
};
pub const Register = enum(u8) {
// 64 bit registers
zero, // zero
ra, // return address. caller saved
sp, // stack pointer. callee saved.
gp, // global pointer
tp, // thread pointer
t0, t1, t2, // temporaries. caller saved.
s0, // s0/fp, callee saved.
s1, // callee saved.
a0, a1, // fn args/return values. caller saved.
a2, a3, a4, a5, a6, a7, // fn args. caller saved.
s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, // saved registers. callee saved.
t3, t4, t5, t6, // caller saved
pub fn parseRegName(name: []const u8) ?Register {
if(std.meta.stringToEnum(Register, name)) |reg| return reg;
if(std.meta.stringToEnum(RawRegister, name)) |rawreg| return @intToEnum(Register, @enumToInt(rawreg));
return null;
}
/// Returns the register's id.
pub fn id(self: @This()) u5 {
return @truncate(u5, @enumToInt(self));
}
/// Returns the index into `callee_preserved_regs`.
pub fn allocIndex(self: Register) ?u4 {
inline for(callee_preserved_regs) |cpreg, i| {
if(self == cpreg) return i;
}
return null;
}
};
// zig fmt: on
pub const callee_preserved_regs = [_]Register{
.s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11,
};

View File

@ -81,6 +81,26 @@ pub const Register = enum(u8) {
else => null,
};
}
/// Convert from any register to its 64 bit alias.
pub fn to64(self: Register) Register {
return @intToEnum(Register, self.id());
}
/// Convert from any register to its 32 bit alias.
pub fn to32(self: Register) Register {
return @intToEnum(Register, @as(u8, self.id()) + 16);
}
/// Convert from any register to its 16 bit alias.
pub fn to16(self: Register) Register {
return @intToEnum(Register, @as(u8, self.id()) + 32);
}
/// Convert from any register to its 8 bit alias.
pub fn to8(self: Register) Register {
return @intToEnum(Register, @as(u8, self.id()) + 48);
}
};
// zig fmt: on
@ -88,3 +108,4 @@ pub const Register = enum(u8) {
/// These registers belong to the called function.
pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };

View File

@ -4,6 +4,7 @@ const Type = @import("type.zig").Type;
const Module = @import("Module.zig");
const assert = std.debug.assert;
const codegen = @import("codegen.zig");
const ast = std.zig.ast;
/// These are in-memory, analyzed instructions. See `zir.Inst` for the representation
/// of instructions that correspond to the ZIR text format.
@ -47,6 +48,7 @@ pub const Inst = struct {
pub const Tag = enum {
add,
alloc,
arg,
assembly,
bitcast,
@ -63,28 +65,34 @@ pub const Inst = struct {
cmp_neq,
condbr,
constant,
dbg_stmt,
isnonnull,
isnull,
/// Read a value from a pointer.
load,
ptrtoint,
ref,
ret,
retvoid,
/// Write a value to a pointer. LHS is pointer, RHS is value.
store,
sub,
unreach,
not,
floatcast,
intcast,
/// There is one-to-one correspondence between tag and type for now,
/// but this will not always be the case. For example, binary operations
/// such as + and - will have different tags but the same type.
pub fn Type(tag: Tag) type {
return switch (tag) {
.alloc,
.retvoid,
.unreach,
.arg,
.breakpoint,
.dbg_stmt,
=> NoOp,
.ref,
.ret,
.bitcast,
.not,
@ -93,6 +101,7 @@ pub const Inst = struct {
.ptrtoint,
.floatcast,
.intcast,
.load,
=> UnOp,
.add,
@ -103,6 +112,7 @@ pub const Inst = struct {
.cmp_gte,
.cmp_gt,
.cmp_neq,
.store,
=> BinOp,
.assembly => Assembly,
@ -157,8 +167,7 @@ pub const Inst = struct {
/// Returns `null` if runtime-known.
pub fn value(base: *Inst) ?Value {
if (base.ty.onePossibleValue())
return Value.initTag(.the_one_possible_value);
if (base.ty.onePossibleValue()) |opv| return opv;
const inst = base.cast(Constant) orelse return null;
return inst.val;

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,7 @@ const Module = @import("Module.zig");
const link = @import("link.zig");
const Package = @import("Package.zig");
const zir = @import("zir.zig");
// TODO Improve async I/O enough that we feel comfortable doing this.
//pub const io_mode = .evented;
const build_options = @import("build_options");
pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
@ -47,18 +45,16 @@ pub fn log(
if (@enumToInt(level) > @enumToInt(std.log.level))
return;
const scope_prefix = "(" ++ switch (scope) {
// Uncomment to hide logs
//.compiler,
.module,
.liveness,
.link,
=> return,
const scope_name = @tagName(scope);
const ok = comptime for (build_options.log_scopes) |log_scope| {
if (mem.eql(u8, log_scope, scope_name))
break true;
} else false;
else => @tagName(scope),
} ++ "): ";
if (!ok)
return;
const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
const prefix = "[" ++ @tagName(level) ++ "] " ++ "(" ++ @tagName(scope) ++ "): ";
// Print the message to stderr, silently ignoring any errors
std.debug.print(prefix ++ format, args);
@ -94,6 +90,8 @@ pub fn main() !void {
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
} else if (mem.eql(u8, cmd, "version")) {
// Need to set up the build script to give the version as a comptime value.
// TODO when you solve this, also take a look at link.zig, there is a placeholder
// that says "TODO version here".
std.debug.print("TODO version command not implemented yet\n", .{});
return error.Unimplemented;
} else if (mem.eql(u8, cmd, "zen")) {
@ -492,6 +490,7 @@ fn buildOutputType(
defer root_pkg.destroy();
var module = try Module.init(gpa, .{
.root_name = root_name,
.target = target_info.target,
.output_mode = output_mode,
.root_pkg = root_pkg,

View File

@ -4,6 +4,11 @@ const Module = @import("Module.zig");
const Allocator = std.mem.Allocator;
const zir = @import("zir.zig");
const Package = @import("Package.zig");
const build_options = @import("build_options");
const enable_qemu: bool = build_options.enable_qemu;
const enable_wine: bool = build_options.enable_wine;
const enable_wasmtime: bool = build_options.enable_wasmtime;
const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_dir;
const cheader = @embedFile("cbe.h");
@ -401,8 +406,6 @@ pub const TestContext = struct {
const root_node = try progress.start("tests", self.cases.items.len);
defer root_node.end();
const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
for (self.cases.items) |case| {
std.testing.base_allocator_instance.reset();
@ -415,13 +418,19 @@ pub const TestContext = struct {
progress.initial_delay_ns = 0;
progress.refresh_rate_ns = 0;
const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);
try self.runOneCase(std.testing.allocator, &prg_node, case, info.target);
try self.runOneCase(std.testing.allocator, &prg_node, case);
try std.testing.allocator_instance.validate();
}
}
fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case, target: std.Target) !void {
fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case) !void {
const target_info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target);
const target = target_info.target;
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
defer arena_allocator.deinit();
const arena = &arena_allocator.allocator;
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
@ -429,10 +438,10 @@ pub const TestContext = struct {
const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
defer root_pkg.destroy();
const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null);
defer allocator.free(bin_name);
const bin_name = try std.zig.binNameAlloc(arena, "test_case", target, case.output_mode, null);
var module = try Module.init(allocator, .{
.root_name = "test_case",
.target = target,
// TODO: support tests for object file building, and library builds
// and linking. This will require a rework to support multi-file
@ -484,8 +493,7 @@ pub const TestContext = struct {
// incremental updates
var file = try tmp.dir.openFile(bin_name, .{ .read = true });
defer file.close();
var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!");
defer allocator.free(out);
var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!");
if (expected_output.len != out.len) {
std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
@ -532,8 +540,7 @@ pub const TestContext = struct {
var test_node = update_node.start("assert", null);
test_node.activate();
defer test_node.end();
var handled_errors = try allocator.alloc(bool, e.len);
defer allocator.free(handled_errors);
var handled_errors = try arena.alloc(bool, e.len);
for (handled_errors) |*h| {
h.* = false;
}
@ -568,14 +575,59 @@ pub const TestContext = struct {
exec_node.activate();
defer exec_node.end();
try module.makeBinFileExecutable();
var argv = std.ArrayList([]const u8).init(allocator);
defer argv.deinit();
const exe_path = try std.fmt.allocPrint(allocator, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name});
defer allocator.free(exe_path);
const exe_path = try std.fmt.allocPrint(arena, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name});
switch (case.target.getExternalExecutor()) {
.native => try argv.append(exe_path),
.unavailable => return, // No executor available; pass test.
.qemu => |qemu_bin_name| if (enable_qemu) {
// TODO Ability for test cases to specify whether to link libc.
const need_cross_glibc = false; // target.isGnuLibC() and self.is_linking_libc;
const glibc_dir_arg = if (need_cross_glibc)
glibc_multi_install_dir orelse return // glibc dir not available; pass test
else
null;
try argv.append(qemu_bin_name);
if (glibc_dir_arg) |dir| {
const linux_triple = try target.linuxTriple(arena);
const full_dir = try std.fs.path.join(arena, &[_][]const u8{
dir,
linux_triple,
});
try argv.append("-L");
try argv.append(full_dir);
}
try argv.append(exe_path);
} else {
return; // QEMU not available; pass test.
},
.wine => |wine_bin_name| if (enable_wine) {
try argv.append(wine_bin_name);
try argv.append(exe_path);
} else {
return; // Wine not available; pass test.
},
.wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
try argv.append(wasmtime_bin_name);
try argv.append("--dir=.");
try argv.append(exe_path);
} else {
return; // wasmtime not available; pass test.
},
}
try module.makeBinFileExecutable();
break :x try std.ChildProcess.exec(.{
.allocator = allocator,
.argv = &[_][]const u8{exe_path},
.argv = argv.items,
.cwd_dir = tmp.dir,
});
};

View File

@ -8,7 +8,6 @@ const Token = std.zig.Token;
usingnamespace @import("clang.zig");
const ctok = std.c.tokenizer;
const CToken = std.c.Token;
const CTokenList = std.c.tokenizer.Source.TokenList;
const mem = std.mem;
const math = std.math;
@ -2864,7 +2863,6 @@ fn transCharLiteral(
"TODO: support character literal kind {}",
.{kind},
),
else => unreachable,
};
if (suppress_as == .no_as) {
return maybeSuppressResult(rp, scope, result_used, int_lit_node);
@ -3070,13 +3068,30 @@ fn transUnaryExprOrTypeTraitExpr(
stmt: *const ZigClangUnaryExprOrTypeTraitExpr,
result_used: ResultUsed,
) TransError!*ast.Node {
const loc = ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt);
const type_node = try transQualType(
rp,
ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(stmt),
ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(stmt),
loc,
);
const builtin_node = try rp.c.createBuiltinCall("@sizeOf", 1);
const kind = ZigClangUnaryExprOrTypeTraitExpr_getKind(stmt);
const kind_str = switch (kind) {
.SizeOf => "@sizeOf",
.AlignOf => "@alignOf",
.PreferredAlignOf,
.VecStep,
.OpenMPRequiredSimdAlign,
=> return revertAndWarn(
rp,
error.UnsupportedTranslation,
loc,
"Unsupported type trait kind {}",
.{kind},
),
};
const builtin_node = try rp.c.createBuiltinCall(kind_str, 1);
builtin_node.params()[0] = type_node;
builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
return maybeSuppressResult(rp, scope, result_used, &builtin_node.base);
@ -4515,7 +4530,7 @@ const CtrlFlow = struct {
const ltoken = try appendToken(c, kw, kw_text);
const label_token = if (label) |l| blk: {
_ = try appendToken(c, .Colon, ":");
break :blk try appendToken(c, .Identifier, l);
break :blk try appendIdentifier(c, l);
} else null;
return CtrlFlow{
.c = c,
@ -5197,16 +5212,39 @@ pub fn freeErrors(errors: []ClangErrMsg) void {
ZigClangErrorMsg_delete(errors.ptr, errors.len);
}
const CTokIterator = struct {
source: []const u8,
list: []const CToken,
i: usize = 0,
fn peek(self: *CTokIterator) ?CToken.Id {
if (self.i >= self.list.len) return null;
return self.list[self.i + 1].id;
}
fn next(self: *CTokIterator) ?CToken.Id {
if (self.i >= self.list.len) return null;
self.i += 1;
return self.list[self.i].id;
}
fn slice(self: *CTokIterator, index: usize) []const u8 {
const tok = self.list[index];
return self.source[tok.start..tok.end];
}
};
fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
// TODO if we see #undef, delete it from the table
var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit);
const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit);
var tok_list = CTokenList.init(c.arena);
var tok_list = std.ArrayList(CToken).init(c.gpa);
defer tok_list.deinit();
const scope = c.global_scope;
while (it.I != it_end.I) : (it.I += 1) {
const entity = ZigClangPreprocessingRecord_iterator_deref(it);
tok_list.shrink(0);
tok_list.items.len = 0;
switch (ZigClangPreprocessedEntity_getKind(entity)) {
.MacroDefinitionKind => {
const macro = @ptrCast(*ZigClangMacroDefinitionRecord, entity);
@ -5224,38 +5262,34 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc);
const slice = begin_c[0..mem.len(begin_c)];
tok_list.shrink(0);
var tokenizer = std.c.Tokenizer{
.source = &std.c.tokenizer.Source{
.buffer = slice,
.file_name = undefined,
.tokens = undefined,
},
.buffer = slice,
};
while (true) {
const tok = tokenizer.next();
switch (tok.id) {
.Nl, .Eof => {
try tok_list.push(tok);
try tok_list.append(tok);
break;
},
.LineComment, .MultiLineComment => continue,
else => {},
}
try tok_list.push(tok);
try tok_list.append(tok);
}
var tok_it = tok_list.iterator(0);
const first_tok = tok_it.next().?;
assert(mem.eql(u8, slice[first_tok.start..first_tok.end], name));
var tok_it = CTokIterator{
.source = slice,
.list = tok_list.items,
};
assert(mem.eql(u8, tok_it.slice(0), name));
var macro_fn = false;
const next = tok_it.peek().?;
switch (next.id) {
switch (tok_it.peek().?) {
.Identifier => {
// if it equals itself, ignore. for example, from stdio.h:
// #define stdin stdin
if (mem.eql(u8, name, slice[next.start..next.end])) {
if (mem.eql(u8, name, tok_it.slice(1))) {
continue;
}
},
@ -5266,15 +5300,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
},
.LParen => {
// if the name is immediately followed by a '(' then it is a function
macro_fn = first_tok.end == next.start;
macro_fn = tok_it.list[0].end == tok_it.list[1].start;
},
else => {},
}
(if (macro_fn)
transMacroFnDefine(c, &tok_it, slice, mangled_name, begin_loc)
transMacroFnDefine(c, &tok_it, mangled_name, begin_loc)
else
transMacroDefine(c, &tok_it, slice, mangled_name, begin_loc)) catch |err| switch (err) {
transMacroDefine(c, &tok_it, mangled_name, begin_loc)) catch |err| switch (err) {
error.ParseError => continue,
error.OutOfMemory => |e| return e,
};
@ -5284,7 +5318,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
}
}
fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
fn transMacroDefine(c: *Context, it: *CTokIterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
const scope = &c.global_scope.base;
const visib_tok = try appendToken(c, .Keyword_pub, "pub");
@ -5292,15 +5326,15 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n
const name_tok = try appendIdentifier(c, name);
const eq_token = try appendToken(c, .Equal, "=");
const init_node = try parseCExpr(c, it, source, source_loc, scope);
const init_node = try parseCExpr(c, it, source_loc, scope);
const last = it.next().?;
if (last.id != .Eof and last.id != .Nl)
if (last != .Eof and last != .Nl)
return failDecl(
c,
source_loc,
name,
"unable to translate C expr: unexpected token .{}",
.{@tagName(last.id)},
.{@tagName(last)},
);
const semicolon_token = try appendToken(c, .Semicolon, ";");
@ -5316,7 +5350,7 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n
_ = try c.global_scope.macro_table.put(name, &node.base);
}
fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
fn transMacroFnDefine(c: *Context, it: *CTokIterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
var block_scope = try Scope.Block.init(c, &c.global_scope.base, null);
defer block_scope.deinit();
const scope = &block_scope.base;
@ -5327,7 +5361,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
const name_tok = try appendIdentifier(c, name);
_ = try appendToken(c, .LParen, "(");
if (it.next().?.id != .LParen) {
if (it.next().? != .LParen) {
return failDecl(
c,
source_loc,
@ -5341,8 +5375,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
defer fn_params.deinit();
while (true) {
const param_tok = it.next().?;
if (param_tok.id != .Identifier) {
if (it.next().? != .Identifier) {
return failDecl(
c,
source_loc,
@ -5352,7 +5385,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
);
}
const mangled_name = try block_scope.makeMangledName(c, source[param_tok.start..param_tok.end]);
const mangled_name = try block_scope.makeMangledName(c, it.slice(it.i));
const param_name_tok = try appendIdentifier(c, mangled_name);
_ = try appendToken(c, .Colon, ":");
@ -5370,13 +5403,13 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
.param_type = .{ .any_type = &any_type.base },
};
if (it.peek().?.id != .Comma)
if (it.peek().? != .Comma)
break;
_ = it.next();
_ = try appendToken(c, .Comma, ",");
}
if (it.next().?.id != .RParen) {
if (it.next().? != .RParen) {
return failDecl(
c,
source_loc,
@ -5391,15 +5424,15 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
const type_of = try c.createBuiltinCall("@TypeOf", 1);
const return_kw = try appendToken(c, .Keyword_return, "return");
const expr = try parseCExpr(c, it, source, source_loc, scope);
const expr = try parseCExpr(c, it, source_loc, scope);
const last = it.next().?;
if (last.id != .Eof and last.id != .Nl)
if (last != .Eof and last != .Nl)
return failDecl(
c,
source_loc,
name,
"unable to translate C expr: unexpected token .{}",
.{@tagName(last.id)},
.{@tagName(last)},
);
_ = try appendToken(c, .Semicolon, ";");
const type_of_arg = if (expr.tag != .Block) expr else blk: {
@ -5436,28 +5469,27 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
const ParseError = Error || error{ParseError};
fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
switch (it.next().?.id) {
fn parseCExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
const node = try parseCPrefixOpExpr(c, it, source_loc, scope);
switch (it.next().?) {
.QuestionMark => {
// must come immediately after expr
_ = try appendToken(c, .RParen, ")");
const if_node = try transCreateNodeIf(c);
if_node.condition = node;
if_node.body = try parseCPrimaryExpr(c, it, source, source_loc, scope);
if (it.next().?.id != .Colon) {
const first_tok = it.list.at(0);
if_node.body = try parseCPrimaryExpr(c, it, source_loc, scope);
if (it.next().? != .Colon) {
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
it.slice(0),
"unable to translate C expr: expected ':'",
.{},
);
return error.ParseError;
}
if_node.@"else" = try transCreateNodeElse(c);
if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source, source_loc, scope);
if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source_loc, scope);
return &if_node.base;
},
.Comma => {
@ -5480,10 +5512,10 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_
};
try block_scope.statements.append(&op_node.base);
last = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
last = try parseCPrefixOpExpr(c, it, source_loc, scope);
_ = try appendToken(c, .Semicolon, ";");
if (it.next().?.id != .Comma) {
_ = it.prev();
if (it.next().? != .Comma) {
it.i -= 1;
break;
}
}
@ -5494,70 +5526,74 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_
return &block_node.base;
},
else => {
_ = it.prev();
it.i -= 1;
return node;
},
}
}
fn parseCNumLit(c: *Context, tok: *CToken, source: []const u8, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {
var lit_bytes = source[tok.start..tok.end];
fn parseCNumLit(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {
var lit_bytes = it.slice(it.i);
if (tok.id == .IntegerLiteral) {
if (lit_bytes.len > 2 and lit_bytes[0] == '0') {
switch (lit_bytes[1]) {
'0'...'7' => {
// Octal
lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes});
},
'X' => {
// Hexadecimal with capital X, valid in C but not in Zig
lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]});
},
else => {},
switch (it.list[it.i].id) {
.IntegerLiteral => |suffix| {
if (lit_bytes.len > 2 and lit_bytes[0] == '0') {
switch (lit_bytes[1]) {
'0'...'7' => {
// Octal
lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes});
},
'X' => {
// Hexadecimal with capital X, valid in C but not in Zig
lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]});
},
else => {},
}
}
}
if (tok.id.IntegerLiteral == .None) {
return transCreateNodeInt(c, lit_bytes);
}
if (suffix == .none) {
return transCreateNodeInt(c, lit_bytes);
}
const cast_node = try c.createBuiltinCall("@as", 2);
cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (tok.id.IntegerLiteral) {
.U => "c_uint",
.L => "c_long",
.LU => "c_ulong",
.LL => "c_longlong",
.LLU => "c_ulonglong",
else => unreachable,
});
lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (tok.id.IntegerLiteral) {
.U, .L => @as(u8, 1),
.LU, .LL => 2,
.LLU => 3,
else => unreachable,
}];
_ = try appendToken(c, .Comma, ",");
cast_node.params()[1] = try transCreateNodeInt(c, lit_bytes);
cast_node.rparen_token = try appendToken(c, .RParen, ")");
return &cast_node.base;
} else if (tok.id == .FloatLiteral) {
if (lit_bytes[0] == '.')
lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes});
if (tok.id.FloatLiteral == .None) {
return transCreateNodeFloat(c, lit_bytes);
}
const cast_node = try c.createBuiltinCall("@as", 2);
cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (tok.id.FloatLiteral) {
.F => "f32",
.L => "c_longdouble",
else => unreachable,
});
_ = try appendToken(c, .Comma, ",");
cast_node.params()[1] = try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]);
cast_node.rparen_token = try appendToken(c, .RParen, ")");
return &cast_node.base;
} else unreachable;
const cast_node = try c.createBuiltinCall("@as", 2);
cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) {
.u => "c_uint",
.l => "c_long",
.lu => "c_ulong",
.ll => "c_longlong",
.llu => "c_ulonglong",
else => unreachable,
});
lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (suffix) {
.u, .l => @as(u8, 1),
.lu, .ll => 2,
.llu => 3,
else => unreachable,
}];
_ = try appendToken(c, .Comma, ",");
cast_node.params()[1] = try transCreateNodeInt(c, lit_bytes);
cast_node.rparen_token = try appendToken(c, .RParen, ")");
return &cast_node.base;
},
.FloatLiteral => |suffix| {
if (lit_bytes[0] == '.')
lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes});
if (suffix == .none) {
return transCreateNodeFloat(c, lit_bytes);
}
const cast_node = try c.createBuiltinCall("@as", 2);
cast_node.params()[0] = try transCreateNodeIdentifier(c, switch (suffix) {
.f => "f32",
.l => "c_longdouble",
else => unreachable,
});
_ = try appendToken(c, .Comma, ",");
cast_node.params()[1] = try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]);
cast_node.rparen_token = try appendToken(c, .RParen, ")");
return &cast_node.base;
},
else => unreachable,
}
}
fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ![]const u8 {
@ -5720,13 +5756,13 @@ fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const
return bytes[0..i];
}
fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
fn parseCPrimaryExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
const tok = it.next().?;
switch (tok.id) {
const slice = it.slice(it.i);
switch (tok) {
.CharLiteral => {
const first_tok = it.list.at(0);
if (source[tok.start] != '\'' or source[tok.start + 1] == '\\' or tok.end - tok.start == 3) {
const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc));
if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) {
const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, slice, it.slice(0), source_loc));
const node = try c.arena.create(ast.Node.OneToken);
node.* = .{
.base = .{ .tag = .CharLiteral },
@ -5734,7 +5770,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
};
return &node.base;
} else {
const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{source[tok.start + 1 .. tok.end - 1]});
const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{slice[1 .. slice.len - 1]});
const node = try c.arena.create(ast.Node.OneToken);
node.* = .{
.base = .{ .tag = .IntegerLiteral },
@ -5744,8 +5780,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
}
},
.StringLiteral => {
const first_tok = it.list.at(0);
const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc));
const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, slice, it.slice(0), source_loc));
const node = try c.arena.create(ast.Node.OneToken);
node.* = .{
.base = .{ .tag = .StringLiteral },
@ -5754,7 +5789,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
return &node.base;
},
.IntegerLiteral, .FloatLiteral => {
return parseCNumLit(c, tok, source, source_loc);
return parseCNumLit(c, it, source_loc);
},
// eventually this will be replaced by std.c.parse which will handle these correctly
.Keyword_void => return transCreateNodeIdentifierUnchecked(c, "c_void"),
@ -5764,22 +5799,50 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
.Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"),
.Keyword_float => return transCreateNodeIdentifierUnchecked(c, "f32"),
.Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"),
.Keyword_char => return transCreateNodeIdentifierUnchecked(c, "c_char"),
.Keyword_unsigned => return transCreateNodeIdentifierUnchecked(c, "c_uint"),
.Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"),
.Keyword_unsigned => if (it.next()) |t| switch (t) {
.Keyword_char => return transCreateNodeIdentifierUnchecked(c, "u8"),
.Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_ushort"),
.Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_uint"),
.Keyword_long => if (it.peek() != null and it.peek().? == .Keyword_long) {
_ = it.next();
return transCreateNodeIdentifierUnchecked(c, "c_ulonglong");
} else return transCreateNodeIdentifierUnchecked(c, "c_ulong"),
else => {
it.i -= 1;
return transCreateNodeIdentifierUnchecked(c, "c_uint");
},
} else {
return transCreateNodeIdentifierUnchecked(c, "c_uint");
},
.Keyword_signed => if (it.next()) |t| switch (t) {
.Keyword_char => return transCreateNodeIdentifierUnchecked(c, "i8"),
.Keyword_short => return transCreateNodeIdentifierUnchecked(c, "c_short"),
.Keyword_int => return transCreateNodeIdentifierUnchecked(c, "c_int"),
.Keyword_long => if (it.peek() != null and it.peek().? == .Keyword_long) {
_ = it.next();
return transCreateNodeIdentifierUnchecked(c, "c_longlong");
} else return transCreateNodeIdentifierUnchecked(c, "c_long"),
else => {
it.i -= 1;
return transCreateNodeIdentifierUnchecked(c, "c_int");
},
} else {
return transCreateNodeIdentifierUnchecked(c, "c_int");
},
.Identifier => {
const mangled_name = scope.getAlias(source[tok.start..tok.end]);
const mangled_name = scope.getAlias(it.slice(it.i));
return transCreateNodeIdentifier(c, mangled_name);
},
.LParen => {
const inner_node = try parseCExpr(c, it, source, source_loc, scope);
const inner_node = try parseCExpr(c, it, source_loc, scope);
const next_id = it.next().?.id;
const next_id = it.next().?;
if (next_id != .RParen) {
const first_tok = it.list.at(0);
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
it.slice(0),
"unable to translate C expr: expected ')'' instead got: {}",
.{@tagName(next_id)},
);
@ -5787,7 +5850,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
}
var saw_l_paren = false;
var saw_integer_literal = false;
switch (it.peek().?.id) {
switch (it.peek().?) {
// (type)(to_cast)
.LParen => {
saw_l_paren = true;
@ -5805,14 +5868,13 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
// hack to get zig fmt to render a comma in builtin calls
_ = try appendToken(c, .Comma, ",");
const node_to_cast = try parseCExpr(c, it, source, source_loc, scope);
const node_to_cast = try parseCExpr(c, it, source_loc, scope);
if (saw_l_paren and it.next().?.id != .RParen) {
const first_tok = it.list.at(0);
if (saw_l_paren and it.next().? != .RParen) {
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
it.slice(0),
"unable to translate C expr: expected ')''",
.{},
);
@ -5843,13 +5905,12 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
return &group_node.base;
},
else => {
const first_tok = it.list.at(0);
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
it.slice(0),
"unable to translate C expr: unexpected token .{}",
.{@tagName(tok.id)},
.{@tagName(tok)},
);
return error.ParseError;
},
@ -5957,61 +6018,52 @@ fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node {
return &group_node.base;
}
fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
var node = try parseCPrimaryExpr(c, it, source, source_loc, scope);
fn parseCSuffixOpExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
var node = try parseCPrimaryExpr(c, it, source_loc, scope);
while (true) {
const tok = it.next().?;
var op_token: ast.TokenIndex = undefined;
var op_id: ast.Node.Tag = undefined;
var bool_op = false;
switch (tok.id) {
switch (it.next().?) {
.Period => {
const name_tok = it.next().?;
if (name_tok.id != .Identifier) {
const first_tok = it.list.at(0);
if (it.next().? != .Identifier) {
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
it.slice(0),
"unable to translate C expr: expected identifier",
.{},
);
return error.ParseError;
}
node = try transCreateNodeFieldAccess(c, node, source[name_tok.start..name_tok.end]);
node = try transCreateNodeFieldAccess(c, node, it.slice(it.i));
continue;
},
.Arrow => {
const name_tok = it.next().?;
if (name_tok.id != .Identifier) {
const first_tok = it.list.at(0);
if (it.next().? != .Identifier) {
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
it.slice(0),
"unable to translate C expr: expected identifier",
.{},
);
return error.ParseError;
}
const deref = try transCreateNodePtrDeref(c, node);
node = try transCreateNodeFieldAccess(c, deref, source[name_tok.start..name_tok.end]);
node = try transCreateNodeFieldAccess(c, deref, it.slice(it.i));
continue;
},
.Asterisk => {
if (it.peek().?.id == .RParen) {
if (it.peek().? == .RParen) {
// type *)
// hack to get zig fmt to render a comma in builtin calls
_ = try appendToken(c, .Comma, ",");
// * token
_ = it.prev();
// last token of `node`
const prev_id = it.prev().?.id;
_ = it.next();
_ = it.next();
const prev_id = it.list[it.i - 1].id;
if (prev_id == .Keyword_void) {
const ptr = try transCreateNodePtrType(c, false, false, .Asterisk);
@ -6082,15 +6134,14 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
},
.LBracket => {
const arr_node = try transCreateNodeArrayAccess(c, node);
arr_node.index_expr = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
arr_node.index_expr = try parseCPrefixOpExpr(c, it, source_loc, scope);
arr_node.rtoken = try appendToken(c, .RBracket, "]");
node = &arr_node.base;
if (it.next().?.id != .RBracket) {
const first_tok = it.list.at(0);
if (it.next().? != .RBracket) {
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
it.slice(0),
"unable to translate C expr: expected ']'",
.{},
);
@ -6103,23 +6154,21 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
var call_params = std.ArrayList(*ast.Node).init(c.gpa);
defer call_params.deinit();
while (true) {
const arg = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
const arg = try parseCPrefixOpExpr(c, it, source_loc, scope);
try call_params.append(arg);
const next = it.next().?;
if (next.id == .Comma)
_ = try appendToken(c, .Comma, ",")
else if (next.id == .RParen)
break
else {
const first_tok = it.list.at(0);
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
"unable to translate C expr: expected ',' or ')'",
.{},
);
return error.ParseError;
switch (it.next().?) {
.Comma => _ = try appendToken(c, .Comma, ","),
.RParen => break,
else => {
try failDecl(
c,
source_loc,
it.slice(0),
"unable to translate C expr: expected ',' or ')'",
.{},
);
return error.ParseError;
},
}
}
const call_node = try ast.Node.Call.alloc(c.arena, call_params.items.len);
@ -6144,23 +6193,21 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
defer init_vals.deinit();
while (true) {
const val = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
const val = try parseCPrefixOpExpr(c, it, source_loc, scope);
try init_vals.append(val);
const next = it.next().?;
if (next.id == .Comma)
_ = try appendToken(c, .Comma, ",")
else if (next.id == .RBrace)
break
else {
const first_tok = it.list.at(0);
try failDecl(
c,
source_loc,
source[first_tok.start..first_tok.end],
"unable to translate C expr: expected ',' or '}}'",
.{},
);
return error.ParseError;
switch (it.next().?) {
.Comma => _ = try appendToken(c, .Comma, ","),
.RBrace => break,
else => {
try failDecl(
c,
source_loc,
it.slice(0),
"unable to translate C expr: expected ',' or '}}'",
.{},
);
return error.ParseError;
},
}
}
const tuple_node = try ast.Node.StructInitializerDot.alloc(c.arena, init_vals.items.len);
@ -6207,22 +6254,22 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
op_id = .ArrayCat;
op_token = try appendToken(c, .PlusPlus, "++");
_ = it.prev();
it.i -= 1;
},
.Identifier => {
op_id = .ArrayCat;
op_token = try appendToken(c, .PlusPlus, "++");
_ = it.prev();
it.i -= 1;
},
else => {
_ = it.prev();
it.i -= 1;
return node;
},
}
const cast_fn = if (bool_op) macroIntToBool else macroBoolToInt;
const lhs_node = try cast_fn(c, node);
const rhs_node = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
const rhs_node = try parseCPrefixOpExpr(c, it, source_loc, scope);
const op_node = try c.arena.create(ast.Node.SimpleInfixOp);
op_node.* = .{
.base = .{ .tag = op_id },
@ -6234,38 +6281,36 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
}
}
fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
const op_tok = it.next().?;
switch (op_tok.id) {
fn parseCPrefixOpExpr(c: *Context, it: *CTokIterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
switch (it.next().?) {
.Bang => {
const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
.Minus => {
const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
.Plus => return try parseCPrefixOpExpr(c, it, source, source_loc, scope),
.Plus => return try parseCPrefixOpExpr(c, it, source_loc, scope),
.Tilde => {
const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
.Asterisk => {
const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
const node = try parseCPrefixOpExpr(c, it, source_loc, scope);
return try transCreateNodePtrDeref(c, node);
},
.Ampersand => {
const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&");
node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);
return &node.base;
},
else => {
_ = it.prev();
return try parseCSuffixOpExpr(c, it, source, source_loc, scope);
it.i -= 1;
return try parseCSuffixOpExpr(c, it, source_loc, scope);
},
}
}

View File

@ -67,6 +67,7 @@ pub const Type = extern union {
.array, .array_u8_sentinel_0 => return .Array,
.single_const_pointer => return .Pointer,
.single_mut_pointer => return .Pointer,
.single_const_pointer_to_comptime_int => return .Pointer,
.const_slice_u8 => return .Pointer,
}
@ -261,6 +262,15 @@ pub const Type = extern union {
};
return Type{ .ptr_otherwise = &new_payload.base };
},
.single_mut_pointer => {
const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", self.ptr_otherwise);
const new_payload = try allocator.create(Payload.SingleMutPointer);
new_payload.* = .{
.base = payload.base,
.pointee_type = try payload.pointee_type.copy(allocator),
};
return Type{ .ptr_otherwise = &new_payload.base };
},
.int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned),
.int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned),
.function => {
@ -368,6 +378,12 @@ pub const Type = extern union {
ty = payload.pointee_type;
continue;
},
.single_mut_pointer => {
const payload = @fieldParentPtr(Payload.SingleMutPointer, "base", ty.ptr_otherwise);
try out_stream.writeAll("*");
ty = payload.pointee_type;
continue;
},
.int_signed => {
const payload = @fieldParentPtr(Payload.IntSigned, "base", ty.ptr_otherwise);
return out_stream.print("i{}", .{payload.bits});
@ -467,6 +483,7 @@ pub const Type = extern union {
.array_u8_sentinel_0,
.array, // TODO check for zero bits
.single_const_pointer,
.single_mut_pointer,
.int_signed, // TODO check for zero bits
.int_unsigned, // TODO check for zero bits
=> true,
@ -493,13 +510,18 @@ pub const Type = extern union {
.u8,
.i8,
.bool,
.array_u8_sentinel_0,
=> return 1,
.fn_noreturn_no_args, // represents machine code; not a pointer
.fn_void_no_args, // represents machine code; not a pointer
.fn_naked_noreturn_no_args, // represents machine code; not a pointer
.fn_ccc_void_no_args, // represents machine code; not a pointer
.function, // represents machine code; not a pointer
.array_u8_sentinel_0,
=> return 1,
=> return switch (target.cpu.arch) {
.riscv64 => 2,
else => 1,
},
.i16, .u16 => return 2,
.i32, .u32 => return 4,
@ -510,6 +532,7 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.single_const_pointer,
.single_mut_pointer,
=> return @divExact(target.cpu.arch.ptrBitWidth(), 8),
.c_short => return @divExact(CType.short.sizeInBits(target), 8),
@ -591,6 +614,7 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.single_const_pointer,
.single_mut_pointer,
=> return @divExact(target.cpu.arch.ptrBitWidth(), 8),
.c_short => return @divExact(CType.short.sizeInBits(target), 8),
@ -671,6 +695,7 @@ pub const Type = extern union {
=> false,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
=> true,
};
@ -714,6 +739,7 @@ pub const Type = extern union {
.array,
.array_u8_sentinel_0,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.fn_noreturn_no_args,
.fn_void_no_args,
@ -728,8 +754,7 @@ pub const Type = extern union {
};
}
/// Asserts the type is a pointer type.
pub fn pointerIsConst(self: Type) bool {
pub fn isConstPtr(self: Type) bool {
return switch (self.tag()) {
.u8,
.i8,
@ -773,7 +798,8 @@ pub const Type = extern union {
.function,
.int_unsigned,
.int_signed,
=> unreachable,
.single_mut_pointer,
=> false,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
@ -782,6 +808,58 @@ pub const Type = extern union {
};
}
pub fn isVolatilePtr(self: Type) bool {
return switch (self.tag()) {
.u8,
.i8,
.u16,
.i16,
.u32,
.i32,
.u64,
.i64,
.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_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,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
=> false,
};
}
/// Asserts the type is a pointer or array type.
pub fn elemType(self: Type) Type {
return switch (self.tag()) {
@ -829,6 +907,7 @@ pub const Type = extern union {
.array => self.cast(Payload.Array).?.elem_type,
.single_const_pointer => self.cast(Payload.SingleConstPointer).?.pointee_type,
.single_mut_pointer => self.cast(Payload.SingleMutPointer).?.pointee_type,
.array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8),
.single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
};
@ -876,6 +955,7 @@ pub const Type = extern union {
.fn_ccc_void_no_args,
.function,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.int_unsigned,
@ -929,6 +1009,7 @@ pub const Type = extern union {
.fn_ccc_void_no_args,
.function,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.int_unsigned,
@ -970,6 +1051,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1024,6 +1106,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1078,6 +1161,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1130,6 +1214,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1211,6 +1296,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1268,6 +1354,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1324,6 +1411,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1380,6 +1468,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1433,6 +1522,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1486,6 +1576,7 @@ pub const Type = extern union {
.@"undefined",
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1559,6 +1650,7 @@ pub const Type = extern union {
.function,
.array,
.single_const_pointer,
.single_mut_pointer,
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
@ -1566,7 +1658,7 @@ pub const Type = extern union {
};
}
pub fn onePossibleValue(self: Type) bool {
pub fn onePossibleValue(self: Type) ?Value {
var ty = self;
while (true) switch (ty.tag()) {
.f16,
@ -1605,21 +1697,32 @@ pub const Type = extern union {
.single_const_pointer_to_comptime_int,
.array_u8_sentinel_0,
.const_slice_u8,
=> return false,
.c_void,
.void,
.noreturn,
.@"null",
.@"undefined",
=> return true,
=> return null,
.int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
.int_signed => return ty.cast(Payload.IntSigned).?.bits == 0,
.void => return Value.initTag(.void_value),
.noreturn => return Value.initTag(.unreachable_value),
.@"null" => return Value.initTag(.null_value),
.@"undefined" => return Value.initTag(.undef),
.int_unsigned => {
if (ty.cast(Payload.IntUnsigned).?.bits == 0) {
return Value.initTag(.zero);
} else {
return null;
}
},
.int_signed => {
if (ty.cast(Payload.IntSigned).?.bits == 0) {
return Value.initTag(.zero);
} else {
return null;
}
},
.array => {
const array = ty.cast(Payload.Array).?;
if (array.len == 0)
return true;
return Value.initTag(.empty_array);
ty = array.elem_type;
continue;
},
@ -1628,6 +1731,11 @@ pub const Type = extern union {
ty = ptr.pointee_type;
continue;
},
.single_mut_pointer => {
const ptr = ty.cast(Payload.SingleMutPointer).?;
ty = ptr.pointee_type;
continue;
},
};
}
@ -1678,6 +1786,7 @@ pub const Type = extern union {
.int_signed,
.array,
.single_const_pointer,
.single_mut_pointer,
=> return false,
};
}
@ -1734,6 +1843,7 @@ pub const Type = extern union {
array_u8_sentinel_0,
array,
single_const_pointer,
single_mut_pointer,
int_signed,
int_unsigned,
function,
@ -1764,6 +1874,12 @@ pub const Type = extern union {
pointee_type: Type,
};
pub const SingleMutPointer = struct {
base: Payload = Payload{ .tag = .single_mut_pointer },
pointee_type: Type,
};
pub const IntSigned = struct {
base: Payload = Payload{ .tag = .int_signed },

View File

@ -63,7 +63,9 @@ pub const Value = extern union {
undef,
zero,
the_one_possible_value, // when the type only has one possible value
void_value,
unreachable_value,
empty_array,
null_value,
bool_true,
bool_false, // See last_no_payload_tag below.
@ -164,7 +166,9 @@ pub const Value = extern union {
.const_slice_u8_type,
.undef,
.zero,
.the_one_possible_value,
.void_value,
.unreachable_value,
.empty_array,
.null_value,
.bool_true,
.bool_false,
@ -285,7 +289,8 @@ pub const Value = extern union {
.null_value => return out_stream.writeAll("null"),
.undef => return out_stream.writeAll("undefined"),
.zero => return out_stream.writeAll("0"),
.the_one_possible_value => return out_stream.writeAll("(one possible value)"),
.void_value => return out_stream.writeAll("{}"),
.unreachable_value => return out_stream.writeAll("unreachable"),
.bool_true => return out_stream.writeAll("true"),
.bool_false => return out_stream.writeAll("false"),
.ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
@ -312,6 +317,7 @@ pub const Value = extern union {
try out_stream.print("&[{}] ", .{elem_ptr.index});
val = elem_ptr.array_ptr;
},
.empty_array => return out_stream.writeAll(".{}"),
.bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
.repeated => {
try out_stream.writeAll("(repeated) ");
@ -388,7 +394,9 @@ pub const Value = extern union {
.undef,
.zero,
.the_one_possible_value,
.void_value,
.unreachable_value,
.empty_array,
.bool_true,
.bool_false,
.null_value,
@ -460,15 +468,18 @@ pub const Value = extern union {
.decl_ref,
.elem_ptr,
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.the_one_possible_value, // An integer with one possible value is always zero.
.undef => unreachable,
.zero,
.bool_false,
=> return BigIntMutable.init(&space.limbs, 0).toConst(),
@ -532,16 +543,19 @@ pub const Value = extern union {
.decl_ref,
.elem_ptr,
.bytes,
.undef,
.repeated,
.float_16,
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.undef => unreachable,
.zero,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return 0,
@ -570,10 +584,9 @@ pub const Value = extern union {
.float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val),
.float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val),
.zero, .the_one_possible_value => 0,
.zero => 0,
.int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int),
// .int_i64 => @intToFloat(f128, self.cast(Payload.Int_i64).?.int),
.int_i64 => @panic("TODO lld: error: undefined symbol: __floatditf"),
.int_i64 => @intToFloat(T, self.cast(Payload.Int_i64).?.int),
.int_big_positive, .int_big_negative => @panic("big int to f128"),
else => unreachable,
@ -637,9 +650,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.the_one_possible_value, // an integer with one possible value is always zero
.zero,
.bool_false,
=> return 0,
@ -714,11 +729,13 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.zero,
.undef,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> return true,
@ -797,13 +814,13 @@ pub const Value = extern union {
// return Value.initPayload(&res_payload.base).copy(allocator);
},
32 => {
var res_payload = Value.Payload.Float_32{.val = self.toFloat(f32)};
var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) };
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
},
64 => {
var res_payload = Value.Payload.Float_64{.val = self.toFloat(f64)};
var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) };
if (!self.eql(Value.initPayload(&res_payload.base)))
return error.Overflow;
return Value.initPayload(&res_payload.base).copy(allocator);
@ -875,7 +892,9 @@ pub const Value = extern union {
.int_i64,
.int_big_positive,
.int_big_negative,
.the_one_possible_value,
.empty_array,
.void_value,
.unreachable_value,
=> unreachable,
.zero => false,
@ -939,10 +958,12 @@ pub const Value = extern union {
.bytes,
.repeated,
.undef,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.zero,
.the_one_possible_value, // an integer with one possible value is always zero
.bool_false,
=> .eq,
@ -964,8 +985,8 @@ pub const Value = extern union {
pub fn order(lhs: Value, rhs: Value) std.math.Order {
const lhs_tag = lhs.tag();
const rhs_tag = rhs.tag();
const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value;
const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value;
const lhs_is_zero = lhs_tag == .zero;
const rhs_is_zero = rhs_tag == .zero;
if (lhs_is_zero) return rhs.orderAgainstZero().invert();
if (rhs_is_zero) return lhs.orderAgainstZero();
@ -1071,9 +1092,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
.empty_array,
=> unreachable,
.the_one_possible_value => Value.initTag(.the_one_possible_value),
.ref_val => self.cast(Payload.RefVal).?.val,
.decl_ref => self.cast(Payload.DeclRef).?.decl.value(),
.elem_ptr => {
@ -1130,7 +1153,6 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
.the_one_possible_value,
.bool_true,
.bool_false,
.null_value,
@ -1147,8 +1169,12 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
.unreachable_value,
=> unreachable,
.empty_array => unreachable, // out of bounds array index
.bytes => {
const int_payload = try allocator.create(Payload.Int_u64);
int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
@ -1175,8 +1201,7 @@ pub const Value = extern union {
return self.tag() == .undef;
}
/// Valid for all types. Asserts the value is not undefined.
/// `.the_one_possible_value` is reported as not null.
/// Valid for all types. Asserts the value is not undefined and not unreachable.
pub fn isNull(self: Value) bool {
return switch (self.tag()) {
.ty,
@ -1221,7 +1246,7 @@ pub const Value = extern union {
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
.the_one_possible_value,
.empty_array,
.bool_true,
.bool_false,
.function,
@ -1238,9 +1263,11 @@ pub const Value = extern union {
.float_32,
.float_64,
.float_128,
.void_value,
=> false,
.undef => unreachable,
.unreachable_value => unreachable,
.null_value => true,
};
}

View File

@ -34,25 +34,63 @@ pub const Inst = struct {
/// These names are used directly as the instruction names in the text format.
pub const Tag = enum {
/// Arithmetic addition, asserts no integer overflow.
add,
/// Twos complement wrapping integer addition.
addwrap,
/// Allocates stack local memory. Its lifetime ends when the block ends that contains
/// this instruction.
/// this instruction. The operand is the type of the allocated object.
alloc,
/// Same as `alloc` except the type is inferred.
alloc_inferred,
/// Array concatenation. `a ++ b`
array_cat,
/// Array multiplication `a ** b`
array_mul,
/// Function parameter value. These must be first in a function's main block,
/// in respective order with the parameters.
arg,
/// Type coercion.
as,
/// Inline assembly.
@"asm",
/// Bitwise AND. `&`
bitand,
/// TODO delete this instruction, it has no purpose.
bitcast,
/// An arbitrary typed pointer, which is to be used as an L-Value, is pointer-casted
/// to a new L-Value. The destination type is given by LHS. The cast is to be evaluated
/// as if it were a bit-cast operation from the operand pointer element type to the
/// provided destination type.
bitcast_lvalue,
/// A typed result location pointer is bitcasted to a new result location pointer.
/// The new result location pointer has an inferred type.
bitcast_result_ptr,
/// Bitwise OR. `|`
bitor,
/// A labeled block of code, which can return a value.
block,
/// Boolean NOT. See also `bitnot`.
boolnot,
/// Return a value from a `Block`.
@"break",
breakpoint,
/// Same as `break` but without an operand; the operand is assumed to be the void value.
breakvoid,
/// Function call.
call,
/// `<`
cmp_lt,
/// `<=`
cmp_lte,
/// `==`
cmp_eq,
/// `>=`
cmp_gte,
/// `>`
cmp_gt,
/// `!=`
cmp_neq,
/// Coerces a result location pointer to a new element type. It is evaluated "backwards"-
/// as type coercion from the new element type to the old element type.
/// LHS is destination element type, RHS is result pointer.
@ -65,8 +103,12 @@ pub const Inst = struct {
coerce_to_ptr_elem,
/// Emit an error message and fail compilation.
compileerror,
/// Conditional branch. Splits control flow based on a boolean condition value.
condbr,
/// Special case, has no textual representation.
@"const",
/// Declares the beginning of a statement. Used for debug info.
dbg_stmt,
/// Represents a pointer to a global decl by name.
declref,
/// Represents a pointer to a global decl by string name.
@ -76,61 +118,108 @@ pub const Inst = struct {
declval,
/// Same as declval but the parameter is a `*Module.Decl` rather than a name.
declval_in_module,
/// Load the value from a pointer.
deref,
/// Arithmetic division. Asserts no integer overflow.
div,
/// Given a pointer to an array, slice, or pointer, returns a pointer to the element at
/// the provided index.
elemptr,
/// Emits a compile error if the operand is not `void`.
ensure_result_used,
/// Emits a compile error if an error is ignored.
ensure_result_non_error,
boolnot,
/// Export the provided Decl as the provided name in the compilation's output object file.
@"export",
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
/// to the named field.
fieldptr,
/// Convert a larger float type to any other float type, possibly causing a loss of precision.
floatcast,
/// Declare a function body.
@"fn",
/// Returns a function type.
fntype,
/// Integer literal.
int,
/// Convert an integer value to another integer type, asserting that the destination type
/// can hold the same mathematical value.
intcast,
/// Make an integer type out of signedness and bit count.
inttype,
/// Return a boolean false if an optional is null. `x != null`
isnonnull,
/// Return a boolean true if an optional is null. `x == null`
isnull,
/// Ambiguously remainder division or modulus. If the computation would possibly have
/// a different value depending on whether the operation is remainder division or modulus,
/// a compile error is emitted. Otherwise the computation is performed.
mod_rem,
/// Arithmetic multiplication. Asserts no integer overflow.
mul,
/// Twos complement wrapping integer multiplication.
mulwrap,
/// Given a reference to a function and a parameter index, returns the
/// type of the parameter. TODO what happens when the parameter is `anytype`?
param_type,
/// An alternative to using `const` for simple primitive values such as `true` or `u8`.
/// TODO flatten so that each primitive has its own ZIR Inst Tag.
primitive,
/// Convert a pointer to a `usize` integer.
ptrtoint,
/// Turns an R-Value into a const L-Value. In other words, it takes a value,
/// stores it in a memory location, and returns a const pointer to it. If the value
/// is `comptime`, the memory location is global static constant data. Otherwise,
/// the memory location is in the stack frame, local to the scope containing the
/// instruction.
ref,
/// Obtains a pointer to the return value.
ret_ptr,
/// Obtains the return type of the in-scope function.
ret_type,
/// Write a value to a pointer.
/// Sends control flow back to the function's callee. Takes an operand as the return value.
@"return",
/// Same as `return` but there is no operand; the operand is implicitly the void value.
returnvoid,
/// Integer shift-left. Zeroes are shifted in from the right hand side.
shl,
/// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type.
shr,
/// Create a const pointer type based on the element type. `*const T`
single_const_ptr_type,
/// Create a mutable pointer type based on the element type. `*T`
single_mut_ptr_type,
/// Write a value to a pointer. For loading, see `deref`.
store,
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
str,
int,
inttype,
ptrtoint,
fieldptr,
deref,
as,
@"asm",
@"unreachable",
@"return",
returnvoid,
@"fn",
fntype,
@"export",
/// Given a reference to a function and a parameter index, returns the
/// type of the parameter. TODO what happens when the parameter is `anytype`?
param_type,
primitive,
intcast,
bitcast,
floatcast,
elemptr,
add,
/// Arithmetic subtraction. Asserts no integer overflow.
sub,
cmp_lt,
cmp_lte,
cmp_eq,
cmp_gte,
cmp_gt,
cmp_neq,
condbr,
isnull,
isnonnull,
/// Twos complement wrapping integer subtraction.
subwrap,
/// Returns the type of a value.
typeof,
/// Asserts control-flow will not reach this instruction. Not safety checked - the compiler
/// will assume the correctness of this instruction.
unreach_nocheck,
/// Asserts control-flow will not reach this instruction. In safety-checked modes,
/// this will generate a call to the panic function unless it can be proven unreachable
/// by the compiler.
@"unreachable",
/// Bitwise XOR. `^`
xor,
pub fn Type(tag: Tag) type {
return switch (tag) {
.arg,
.breakpoint,
.@"unreachable",
.dbg_stmt,
.returnvoid,
.alloc_inferred,
.ret_ptr,
.ret_type,
.unreach_nocheck,
.@"unreachable",
=> NoOp,
.boolnot,
@ -143,10 +232,28 @@ pub const Inst = struct {
.ensure_result_used,
.ensure_result_non_error,
.bitcast_result_ptr,
.ref,
.bitcast_lvalue,
.typeof,
.single_const_ptr_type,
.single_mut_ptr_type,
=> UnOp,
.add,
.addwrap,
.array_cat,
.array_mul,
.bitand,
.bitor,
.div,
.mod_rem,
.mul,
.mulwrap,
.shl,
.shr,
.store,
.sub,
.subwrap,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@ -158,6 +265,7 @@ pub const Inst = struct {
.intcast,
.bitcast,
.coerce_result_ptr,
.xor,
=> BinOp,
.block => Block,
@ -172,7 +280,6 @@ pub const Inst = struct {
.coerce_result_block_ptr => CoerceResultBlockPtr,
.compileerror => CompileError,
.@"const" => Const,
.store => Store,
.str => Str,
.int => Int,
.inttype => IntType,
@ -192,63 +299,83 @@ pub const Inst = struct {
/// Function calls do not count.
pub fn isNoReturn(tag: Tag) bool {
return switch (tag) {
.add,
.addwrap,
.alloc,
.alloc_inferred,
.array_cat,
.array_mul,
.arg,
.bitcast_result_ptr,
.block,
.breakpoint,
.call,
.coerce_result_ptr,
.coerce_result_block_ptr,
.coerce_to_ptr_elem,
.@"const",
.declref,
.declref_str,
.declval,
.declval_in_module,
.ensure_result_used,
.ensure_result_non_error,
.ret_ptr,
.ret_type,
.store,
.str,
.int,
.inttype,
.ptrtoint,
.fieldptr,
.deref,
.as,
.@"asm",
.@"fn",
.fntype,
.@"export",
.param_type,
.primitive,
.intcast,
.bitand,
.bitcast,
.floatcast,
.elemptr,
.add,
.sub,
.bitcast_lvalue,
.bitcast_result_ptr,
.bitor,
.block,
.boolnot,
.breakpoint,
.call,
.cmp_lt,
.cmp_lte,
.cmp_eq,
.cmp_gte,
.cmp_gt,
.cmp_neq,
.isnull,
.coerce_result_ptr,
.coerce_result_block_ptr,
.coerce_to_ptr_elem,
.@"const",
.dbg_stmt,
.declref,
.declref_str,
.declval,
.declval_in_module,
.deref,
.div,
.elemptr,
.ensure_result_used,
.ensure_result_non_error,
.@"export",
.floatcast,
.fieldptr,
.@"fn",
.fntype,
.int,
.intcast,
.inttype,
.isnonnull,
.boolnot,
.isnull,
.mod_rem,
.mul,
.mulwrap,
.param_type,
.primitive,
.ptrtoint,
.ref,
.ret_ptr,
.ret_type,
.shl,
.shr,
.single_const_ptr_type,
.single_mut_ptr_type,
.store,
.str,
.sub,
.subwrap,
.typeof,
.xor,
=> false,
.condbr,
.@"unreachable",
.@"return",
.returnvoid,
.@"break",
.breakvoid,
.condbr,
.compileerror,
.@"return",
.returnvoid,
.unreach_nocheck,
.@"unreachable",
=> true,
};
}
@ -430,17 +557,6 @@ pub const Inst = struct {
kw_args: struct {},
};
pub const Store = struct {
pub const base_tag = Tag.store;
base: Inst,
positionals: struct {
ptr: *Inst,
value: *Inst,
},
kw_args: struct {},
};
pub const Str = struct {
pub const base_tag = Tag.str;
base: Inst,
@ -630,7 +746,7 @@ pub const Inst = struct {
.@"false" => .{ .ty = Type.initTag(.bool), .val = Value.initTag(.bool_false) },
.@"null" => .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value) },
.@"undefined" => .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef) },
.void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.the_one_possible_value) },
.void_value => .{ .ty = Type.initTag(.void), .val = Value.initTag(.void_value) },
};
}
};
@ -1486,6 +1602,21 @@ const EmitZIR = struct {
const decl = decl_ref.decl;
return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl));
}
if (typed_value.val.isUndef()) {
const as_inst = try self.arena.allocator.create(Inst.BinOp);
as_inst.* = .{
.base = .{
.tag = .as,
.src = src,
},
.positionals = .{
.lhs = (try self.emitType(src, typed_value.ty)).inst,
.rhs = (try self.emitPrimitive(src, .@"undefined")).inst,
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&as_inst.base);
}
switch (typed_value.ty.zigTypeTag()) {
.Pointer => {
const ptr_elem_type = typed_value.ty.elemType();
@ -1716,15 +1847,19 @@ const EmitZIR = struct {
.breakpoint => try self.emitNoOp(inst.src, .breakpoint),
.unreach => try self.emitNoOp(inst.src, .@"unreachable"),
.retvoid => try self.emitNoOp(inst.src, .returnvoid),
.dbg_stmt => try self.emitNoOp(inst.src, .dbg_stmt),
.not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot),
.ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"),
.ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint),
.isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull),
.isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull),
.load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref),
.ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref),
.add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add),
.sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub),
.store => try self.emitBinOp(inst.src, new_body, inst.castTag(.store).?, .store),
.cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt),
.cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte),
.cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq),
@ -1736,6 +1871,21 @@ const EmitZIR = struct {
.intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast),
.floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, .floatcast),
.alloc => blk: {
const new_inst = try self.arena.allocator.create(Inst.UnOp);
new_inst.* = .{
.base = .{
.src = inst.src,
.tag = .alloc,
},
.positionals = .{
.operand = (try self.emitType(inst.src, inst.ty)).inst,
},
.kw_args = .{},
};
break :blk &new_inst.base;
},
.block => blk: {
const old_inst = inst.castTag(.block).?;
const new_inst = try self.arena.allocator.create(Inst.Block);
@ -1973,6 +2123,25 @@ const EmitZIR = struct {
};
return self.emitUnnamedDecl(&inttype_inst.base);
},
.Pointer => {
if (ty.isSinglePointer()) {
const inst = try self.arena.allocator.create(Inst.UnOp);
const tag: Inst.Tag = if (ty.isConstPtr()) .single_const_ptr_type else .single_mut_ptr_type;
inst.* = .{
.base = .{
.src = src,
.tag = tag,
},
.positionals = .{
.operand = (try self.emitType(src, ty.elemType())).inst,
},
.kw_args = .{},
};
return self.emitUnnamedDecl(&inst.base);
} else {
std.debug.panic("TODO implement emitType for {}", .{ty});
}
},
else => std.debug.panic("TODO implement emitType for {}", .{ty}),
},
}

1160
src-self-hosted/zir_sema.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4110,7 +4110,7 @@ struct IrInstSrcCheckSwitchProngs {
IrInstSrc *target_value;
IrInstSrcCheckSwitchProngsRange *ranges;
size_t range_count;
bool have_else_prong;
AstNode* else_prong;
bool have_underscore_prong;
};

View File

@ -1490,6 +1490,20 @@ static OnePossibleValue type_val_resolve_has_one_possible_value(CodeGen *g, ZigV
}
ZigType *analyze_type_expr(CodeGen *g, Scope *scope, AstNode *node) {
Error err;
// Hot path for simple identifiers, to avoid unnecessary memory allocations.
if (node->type == NodeTypeSymbol) {
Buf *variable_name = node->data.symbol_expr.symbol;
if (buf_eql_str(variable_name, "_"))
goto abort_hot_path;
ZigType *primitive_type;
if ((err = get_primitive_type(g, variable_name, &primitive_type))) {
goto abort_hot_path;
} else {
return primitive_type;
}
abort_hot_path:;
}
ZigValue *result = analyze_const_value(g, scope, node, g->builtin_types.entry_type,
nullptr, UndefBad);
if (type_is_invalid(result->type))

View File

@ -4300,14 +4300,14 @@ static IrInstGen *ir_build_err_to_int_gen(IrAnalyze *ira, Scope *scope, AstNode
static IrInstSrc *ir_build_check_switch_prongs(IrBuilderSrc *irb, Scope *scope, AstNode *source_node,
IrInstSrc *target_value, IrInstSrcCheckSwitchProngsRange *ranges, size_t range_count,
bool have_else_prong, bool have_underscore_prong)
AstNode* else_prong, bool have_underscore_prong)
{
IrInstSrcCheckSwitchProngs *instruction = ir_build_instruction<IrInstSrcCheckSwitchProngs>(
irb, scope, source_node);
instruction->target_value = target_value;
instruction->ranges = ranges;
instruction->range_count = range_count;
instruction->have_else_prong = have_else_prong;
instruction->else_prong = else_prong;
instruction->have_underscore_prong = have_underscore_prong;
ir_ref_instruction(target_value, irb->current_basic_block);
@ -9347,7 +9347,7 @@ static IrInstSrc *ir_gen_switch_expr(IrBuilderSrc *irb, Scope *scope, AstNode *n
}
IrInstSrc *switch_prongs_void = ir_build_check_switch_prongs(irb, scope, node, target_value,
check_ranges.items, check_ranges.length, else_prong != nullptr, underscore_prong != nullptr);
check_ranges.items, check_ranges.length, else_prong, underscore_prong != nullptr);
IrInstSrc *br_instruction;
if (cases.length == 0) {
@ -20604,17 +20604,25 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
return ira->codegen->invalid_inst_gen;
}
ZigType *expected_return_type = result_loc->value->type->data.pointer.child_type;
IrInstGen *dummy_value = ir_const(ira, source_instr, return_type);
dummy_value->value->special = ConstValSpecialRuntime;
IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr,
dummy_value, result_loc->value->type->data.pointer.child_type);
if (type_is_invalid(dummy_result->value->type))
dummy_value, expected_return_type);
if (type_is_invalid(dummy_result->value->type)) {
if ((return_type->id == ZigTypeIdErrorUnion || return_type->id == ZigTypeIdErrorSet) &&
expected_return_type->id != ZigTypeIdErrorUnion && expected_return_type->id != ZigTypeIdErrorSet)
{
add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg,
ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error"));
}
return ira->codegen->invalid_inst_gen;
ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
if (res_child_type == ira->codegen->builtin_types.entry_anytype) {
res_child_type = return_type;
}
if (!handle_is_ptr(ira->codegen, res_child_type)) {
if (expected_return_type == ira->codegen->builtin_types.entry_anytype) {
expected_return_type = return_type;
}
if (!handle_is_ptr(ira->codegen, expected_return_type)) {
ir_reset_result(call_result_loc);
result_loc = nullptr;
}
@ -28828,7 +28836,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
buf_ptr(enum_field->name)));
}
}
} else if (!instruction->have_else_prong) {
} else if (instruction->else_prong == nullptr) {
if (switch_type->data.enumeration.non_exhaustive) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("switch on non-exhaustive enum must include `else` or `_` prong"));
@ -28843,6 +28851,10 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
buf_ptr(enum_field->name)));
}
}
} else if(!switch_type->data.enumeration.non_exhaustive && switch_type->data.enumeration.src_field_count == instruction->range_count) {
ir_add_error_node(ira, instruction->else_prong,
buf_sprintf("unreachable else prong, all cases already handled"));
return ira->codegen->invalid_inst_gen;
}
} else if (switch_type->id == ZigTypeIdErrorSet) {
if (!resolve_inferred_error_set(ira->codegen, switch_type, target_value->base.source_node)) {
@ -28889,7 +28901,7 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
}
field_prev_uses[start_index] = start_value->base.source_node;
}
if (!instruction->have_else_prong) {
if (instruction->else_prong == nullptr) {
if (type_is_global_error_set(switch_type)) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("else prong required when switching on type 'anyerror'"));
@ -28951,16 +28963,20 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
return ira->codegen->invalid_inst_gen;
}
}
if (!instruction->have_else_prong) {
BigInt min_val;
eval_min_max_value_int(ira->codegen, switch_type, &min_val, false);
BigInt max_val;
eval_min_max_value_int(ira->codegen, switch_type, &max_val, true);
if (!rangeset_spans(&rs, &min_val, &max_val)) {
bool handles_all_cases = rangeset_spans(&rs, &min_val, &max_val);
if (!handles_all_cases && instruction->else_prong == nullptr) {
ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities"));
return ira->codegen->invalid_inst_gen;
} else if(handles_all_cases && instruction->else_prong != nullptr) {
ir_add_error_node(ira, instruction->else_prong,
buf_sprintf("unreachable else prong, all cases already handled"));
return ira->codegen->invalid_inst_gen;
}
}
} else if (switch_type->id == ZigTypeIdBool) {
int seenTrue = 0;
int seenFalse = 0;
@ -28990,11 +29006,17 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
return ira->codegen->invalid_inst_gen;
}
}
if (((seenTrue < 1) || (seenFalse < 1)) && !instruction->have_else_prong) {
if (((seenTrue < 1) || (seenFalse < 1)) && instruction->else_prong == nullptr) {
ir_add_error(ira, &instruction->base.base, buf_sprintf("switch must handle all possibilities"));
return ira->codegen->invalid_inst_gen;
}
} else if (!instruction->have_else_prong) {
if(seenTrue == 1 && seenFalse == 1 && instruction->else_prong != nullptr) {
ir_add_error_node(ira, instruction->else_prong,
buf_sprintf("unreachable else prong, all cases already handled"));
return ira->codegen->invalid_inst_gen;
}
} else if (instruction->else_prong == nullptr) {
ir_add_error(ira, &instruction->base.base,
buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name)));
return ira->codegen->invalid_inst_gen;
@ -29077,6 +29099,19 @@ static IrInstGen *ir_align_cast(IrAnalyze *ira, IrInstGen *target, uint32_t alig
ZigType *result_type;
uint32_t old_align_bytes;
ZigType *actual_ptr = target_type;
if (actual_ptr->id == ZigTypeIdOptional) {
actual_ptr = actual_ptr->data.maybe.child_type;
} else if (is_slice(actual_ptr)) {
actual_ptr = actual_ptr->data.structure.fields[slice_ptr_index]->type_entry;
}
if (safety_check_on && !type_has_bits(ira->codegen, actual_ptr)) {
ir_add_error(ira, &target->base,
buf_sprintf("cannot adjust alignment of zero sized type '%s'", buf_ptr(&target_type->name)));
return ira->codegen->invalid_inst_gen;
}
if (target_type->id == ZigTypeIdPointer) {
result_type = adjust_ptr_align(ira->codegen, target_type, align_bytes);
if ((err = resolve_ptr_align(ira, target_type, &old_align_bytes)))
@ -30894,6 +30929,13 @@ static IrInstGen *ir_analyze_instruction_end_expr(IrAnalyze *ira, IrInstSrcEndEx
IrInstGen *store_ptr = ir_analyze_store_ptr(ira, &instruction->base.base, result_loc, value,
instruction->result_loc->allow_write_through_const);
if (type_is_invalid(store_ptr->value->type)) {
if (instruction->result_loc->id == ResultLocIdReturn &&
(value->value->type->id == ZigTypeIdErrorUnion || value->value->type->id == ZigTypeIdErrorSet) &&
ira->explicit_return_type->id != ZigTypeIdErrorUnion && ira->explicit_return_type->id != ZigTypeIdErrorSet)
{
add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg,
ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error"));
}
return ira->codegen->invalid_inst_gen;
}
}

View File

@ -2175,7 +2175,7 @@ static void ir_print_check_switch_prongs(IrPrintSrc *irp, IrInstSrcCheckSwitchPr
fprintf(irp->f, "...");
ir_print_other_inst_src(irp, instruction->ranges[i].end);
}
const char *have_else_str = instruction->have_else_prong ? "yes" : "no";
const char *have_else_str = instruction->else_prong != nullptr ? "yes" : "no";
fprintf(irp->f, ")else:%s", have_else_str);
}

View File

@ -2107,6 +2107,10 @@ static void construct_linker_job_wasm(LinkJob *lj) {
lj->args.append("-z");
lj->args.append(buf_ptr(buf_sprintf("stack-size=%" ZIG_PRI_usize, stack_size)));
// put stack before globals so that stack overflow results in segfault immediately before corrupting globals
// see https://github.com/ziglang/zig/issues/4496
lj->args.append("--stack-first");
if (g->out_type != OutTypeExe) {
lj->args.append("--no-entry"); // So lld doesn't look for _start.

View File

@ -2823,6 +2823,14 @@ struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(
return bitcast(casted->getBeginLoc());
}
enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind(
const struct ZigClangUnaryExprOrTypeTraitExpr *self)
{
auto casted = reinterpret_cast<const clang::UnaryExprOrTypeTraitExpr *>(self);
return (ZigClangUnaryExprOrTypeTrait_Kind)casted->getKind();
}
const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *self) {
auto casted = reinterpret_cast<const clang::DoStmt *>(self);
return reinterpret_cast<const struct ZigClangStmt *>(casted->getBody());

View File

@ -901,6 +901,14 @@ enum ZigClangExpr_ConstExprUsage {
ZigClangExpr_EvaluateForMangling,
};
enum ZigClangUnaryExprOrTypeTrait_Kind {
ZigClangUnaryExprOrTypeTrait_KindSizeOf,
ZigClangUnaryExprOrTypeTrait_KindAlignOf,
ZigClangUnaryExprOrTypeTrait_KindVecStep,
ZigClangUnaryExprOrTypeTrait_KindOpenMPRequiredSimdAlign,
ZigClangUnaryExprOrTypeTrait_KindPreferredAlignOf,
};
ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangSourceManager_getSpellingLoc(const struct ZigClangSourceManager *,
struct ZigClangSourceLocation Loc);
ZIG_EXTERN_C const char *ZigClangSourceManager_getFilename(const struct ZigClangSourceManager *,
@ -1190,6 +1198,7 @@ ZIG_EXTERN_C const struct ZigClangExpr *ZigClangArraySubscriptExpr_getIdx(const
ZIG_EXTERN_C struct ZigClangQualType ZigClangUnaryExprOrTypeTraitExpr_getTypeOfArgument(const struct ZigClangUnaryExprOrTypeTraitExpr *);
ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangUnaryExprOrTypeTraitExpr_getBeginLoc(const struct ZigClangUnaryExprOrTypeTraitExpr *);
ZIG_EXTERN_C enum ZigClangUnaryExprOrTypeTrait_Kind ZigClangUnaryExprOrTypeTraitExpr_getKind(const struct ZigClangUnaryExprOrTypeTraitExpr *);
ZIG_EXTERN_C const struct ZigClangStmt *ZigClangDoStmt_getBody(const struct ZigClangDoStmt *);
ZIG_EXTERN_C const struct ZigClangExpr *ZigClangDoStmt_getCond(const struct ZigClangDoStmt *);

View File

@ -2,6 +2,32 @@ const tests = @import("tests.zig");
const std = @import("std");
pub fn addCases(cases: *tests.CompileErrorContext) void {
cases.addTest("@alignCast of zero sized types",
\\export fn foo() void {
\\ const a: *void = undefined;
\\ _ = @alignCast(2, a);
\\}
\\export fn bar() void {
\\ const a: ?*void = undefined;
\\ _ = @alignCast(2, a);
\\}
\\export fn baz() void {
\\ const a: []void = undefined;
\\ _ = @alignCast(2, a);
\\}
\\export fn qux() void {
\\ const a = struct {
\\ fn a(comptime b: u32) void {}
\\ }.a;
\\ _ = @alignCast(2, a);
\\}
, &[_][]const u8{
"tmp.zig:3:23: error: cannot adjust alignment of zero sized type '*void'",
"tmp.zig:7:23: error: cannot adjust alignment of zero sized type '?*void'",
"tmp.zig:11:23: error: cannot adjust alignment of zero sized type '[]void'",
"tmp.zig:17:23: error: cannot adjust alignment of zero sized type 'fn(u32) anytype'",
});
cases.addTest("invalid pointer with @Type",
\\export fn entry() void {
\\ _ = @Type(.{ .Pointer = .{
@ -18,6 +44,28 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:2:16: error: sentinels are only allowed on slices and unknown-length pointers",
});
cases.addTest("helpful return type error message",
\\export fn foo() u32 {
\\ return error.Ohno;
\\}
\\fn bar() !u32 {
\\ return error.Ohno;
\\}
\\export fn baz() void {
\\ try bar();
\\}
\\export fn quux() u32 {
\\ return bar();
\\}
, &[_][]const u8{
"tmp.zig:2:17: error: expected type 'u32', found 'error{Ohno}'",
"tmp.zig:1:17: note: function cannot return an error",
"tmp.zig:8:5: error: expected type 'void', found '@TypeOf(bar).ReturnType.ErrorSet'",
"tmp.zig:7:17: note: function cannot return an error",
"tmp.zig:11:15: error: expected type 'u32', found '@TypeOf(bar).ReturnType.ErrorSet!u32'",
"tmp.zig:10:18: note: function cannot return an error",
});
cases.addTest("int/float conversion to comptime_int/float",
\\export fn foo() void {
\\ var a: f32 = 2;
@ -509,6 +557,102 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:12:5: error: switch on non-exhaustive enum must include `else` or `_` prong",
});
cases.add("switch expression - unreachable else prong (bool)",
\\fn foo(x: bool) void {
\\ switch (x) {
\\ true => {},
\\ false => {},
\\ else => {},
\\ }
\\}
\\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
, &[_][]const u8{
"tmp.zig:5:9: error: unreachable else prong, all cases already handled",
});
cases.add("switch expression - unreachable else prong (u1)",
\\fn foo(x: u1) void {
\\ switch (x) {
\\ 0 => {},
\\ 1 => {},
\\ else => {},
\\ }
\\}
\\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
, &[_][]const u8{
"tmp.zig:5:9: error: unreachable else prong, all cases already handled",
});
cases.add("switch expression - unreachable else prong (u2)",
\\fn foo(x: u2) void {
\\ switch (x) {
\\ 0 => {},
\\ 1 => {},
\\ 2 => {},
\\ 3 => {},
\\ else => {},
\\ }
\\}
\\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
, &[_][]const u8{
"tmp.zig:7:9: error: unreachable else prong, all cases already handled",
});
cases.add("switch expression - unreachable else prong (range u8)",
\\fn foo(x: u8) void {
\\ switch (x) {
\\ 0 => {},
\\ 1 => {},
\\ 2 => {},
\\ 3 => {},
\\ 4...255 => {},
\\ else => {},
\\ }
\\}
\\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
, &[_][]const u8{
"tmp.zig:8:9: error: unreachable else prong, all cases already handled",
});
cases.add("switch expression - unreachable else prong (range i8)",
\\fn foo(x: i8) void {
\\ switch (x) {
\\ -128...0 => {},
\\ 1 => {},
\\ 2 => {},
\\ 3 => {},
\\ 4...127 => {},
\\ else => {},
\\ }
\\}
\\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
, &[_][]const u8{
"tmp.zig:8:9: error: unreachable else prong, all cases already handled",
});
cases.add("switch expression - unreachable else prong (enum)",
\\const TestEnum = enum{ T1, T2 };
\\
\\fn err(x: u8) TestEnum {
\\ switch (x) {
\\ 0 => return TestEnum.T1,
\\ else => return TestEnum.T2,
\\ }
\\}
\\
\\fn foo(x: u8) void {
\\ switch (err(x)) {
\\ TestEnum.T1 => {},
\\ TestEnum.T2 => {},
\\ else => {},
\\ }
\\}
\\
\\export fn entry() usize { return @sizeOf(@TypeOf(foo)); }
, &[_][]const u8{
"tmp.zig:14:9: error: unreachable else prong, all cases already handled",
});
cases.addTest("@export with empty name string",
\\pub export fn entry() void { }
\\comptime {

View File

@ -7,6 +7,5 @@ test "issue 1111 fixed" {
switch (v) {
Foo.Bar => return,
else => return,
}
}

View File

@ -7,6 +7,11 @@ const linux_x64 = std.zig.CrossTarget{
.os_tag = .linux,
};
const linux_riscv64 = std.zig.CrossTarget{
.cpu_arch = .riscv64,
.os_tag = .linux,
};
pub fn addCases(ctx: *TestContext) !void {
if (std.Target.current.os.tag != .linux or
std.Target.current.cpu.arch != .x86_64)
@ -118,6 +123,42 @@ pub fn addCases(ctx: *TestContext) !void {
\\
);
}
{
var case = ctx.exe("hello world", linux_riscv64);
// Regular old hello world
case.addCompareOutput(
\\export fn _start() noreturn {
\\ print();
\\
\\ exit();
\\}
\\
\\fn print() void {
\\ asm volatile ("ecall"
\\ :
\\ : [number] "{a7}" (64),
\\ [arg1] "{a0}" (1),
\\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")),
\\ [arg3] "{a2}" ("Hello, World!\n".len)
\\ : "rcx", "r11", "memory"
\\ );
\\ return;
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("ecall"
\\ :
\\ : [number] "{a7}" (94),
\\ [arg1] "{a0}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"Hello, World!\n",
);
}
{
var case = ctx.exe("adding numbers at comptime", linux_x64);
@ -333,5 +374,69 @@ pub fn addCases(ctx: *TestContext) !void {
,
"",
);
// Now we test integer return values.
case.addCompareOutput(
\\export fn _start() noreturn {
\\ assert(add(3, 4) == 7);
\\ assert(add(20, 10) == 30);
\\
\\ exit();
\\}
\\
\\fn add(a: u32, b: u32) u32 {
\\ return a + b;
\\}
\\
\\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;
\\}
,
"",
);
// Local mutable variables.
case.addCompareOutput(
\\export fn _start() noreturn {
\\ assert(add(3, 4) == 7);
\\ assert(add(20, 10) == 30);
\\
\\ exit();
\\}
\\
\\fn add(a: u32, b: u32) u32 {
\\ var x: u32 = undefined;
\\ x = 0;
\\ x += a;
\\ x += b;
\\ return x;
\\}
\\
\\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;
\\}
,
"",
);
}
}

View File

@ -3,6 +3,16 @@ const std = @import("std");
const CrossTarget = std.zig.CrossTarget;
pub fn addCases(cases: *tests.TranslateCContext) void {
cases.add("alignof",
\\int main() {
\\ int a = _Alignof(int);
\\}
, &[_][]const u8{
\\pub export fn main() c_int {
\\ var a: c_int = @bitCast(c_int, @truncate(c_uint, @alignOf(c_int)));
\\}
});
cases.add("initializer list macro",
\\typedef struct Color {
\\ unsigned char r;
@ -2715,6 +2725,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\pub const BAR = (@import("std").meta.cast(?*c_void, a));
});
cases.add("macro with cast to unsigned short, long, and long long",
\\#define CURLAUTH_BASIC_BUT_USHORT ((unsigned short) 1)
\\#define CURLAUTH_BASIC ((unsigned long) 1)
\\#define CURLAUTH_BASIC_BUT_ULONGLONG ((unsigned long long) 1)
, &[_][]const u8{
\\pub const CURLAUTH_BASIC_BUT_USHORT = (@import("std").meta.cast(c_ushort, 1));
\\pub const CURLAUTH_BASIC = (@import("std").meta.cast(c_ulong, 1));
\\pub const CURLAUTH_BASIC_BUT_ULONGLONG = (@import("std").meta.cast(c_ulonglong, 1));
});
cases.add("macro conditional operator",
\\#define FOO a ? b : c
, &[_][]const u8{