---
ci/azure/windows_msvc_script.bat | 2 +-
doc/langref.html.in | 24 ++++++++++++------------
src/AstGen.zig | 4 ++--
src/Compilation.zig | 2 +-
src/Liveness.zig | 2 +-
src/Module.zig | 14 +++++++-------
src/Sema.zig | 6 +++---
src/codegen.zig | 14 +++++++-------
src/codegen/arm.zig | 2 +-
src/codegen/llvm.zig | 2 +-
src/codegen/spirv.zig | 4 ++--
src/codegen/wasm.zig | 6 +++---
src/link/Coff.zig | 6 +++---
src/link/MachO.zig | 8 ++++----
src/link/MachO/Atom.zig | 2 +-
src/link/MachO/Trie.zig | 2 +-
src/link/Plan9/aout.zig | 2 +-
src/link/SpirV.zig | 4 ++--
src/link/Wasm.zig | 6 +++---
src/stage1/analyze.cpp | 2 +-
src/stage1/ir.cpp | 6 +++---
src/stage1/ir_print.cpp | 4 ++--
src/stage1/parser.cpp | 2 +-
src/type.zig | 2 +-
src/windows_com.hpp | 2 +-
src/zig_clang.cpp | 8 ++++----
src/zig_clang.h | 8 ++++----
src/zig_llvm-ar.cpp | 2 +-
test/behavior/array_stage1.zig | 2 +-
test/behavior/bugs/1735.zig | 2 +-
test/behavior/eval_stage1.zig | 2 +-
test/behavior/switch.zig | 14 +++++++-------
test/behavior/vector.zig | 2 +-
test/compile_errors.zig | 12 ++++++------
test/run_translated_c.zig | 4 ++--
test/stage2/darwin.zig | 2 +-
test/tests.zig | 2 +-
test/translate_c.zig | 4 ++--
38 files changed, 97 insertions(+), 97 deletions(-)
diff --git a/ci/azure/windows_msvc_script.bat b/ci/azure/windows_msvc_script.bat
index a073650717..84b19ef2fa 100644
--- a/ci/azure/windows_msvc_script.bat
+++ b/ci/azure/windows_msvc_script.bat
@@ -1,7 +1,7 @@
@echo on
SET "SRCROOT=%cd%"
SET "PREVPATH=%PATH%"
-SET "PREVMSYSEM=%MSYSTEM%"
+SET "PREVMSYSTEM=%MSYSTEM%"
set "PATH=%CD:~0,2%\msys64\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem"
SET "MSYSTEM=MINGW64"
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 97503aed72..561065bc0d 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -2125,7 +2125,7 @@ fn dump(args: anytype) !void {
{#header_open|Multidimensional Arrays#}
- Mutlidimensional arrays can be created by nesting arrays:
+ Multidimensional arrays can be created by nesting arrays:
{#code_begin|test|multidimensional#}
const std = @import("std");
@@ -2898,7 +2898,7 @@ fn bar(x: *const u3) u3 {
}
{#code_end#}
- In this case, the function {#syntax#}bar{#endsyntax#} cannot be called becuse the pointer
+ In this case, the function {#syntax#}bar{#endsyntax#} cannot be called because the pointer
to the non-ABI-aligned field mentions the bit offset, but the function expects an ABI-aligned pointer.
@@ -5549,7 +5549,7 @@ test "coerce to optionals" {
}
{#code_end#}
It works nested inside the {#link|Error Union Type#}, too:
- {#code_begin|test|test_corerce_optional_wrapped_error_union#}
+ {#code_begin|test|test_coerce_optional_wrapped_error_union#}
const std = @import("std");
const expect = std.testing.expect;
@@ -7669,7 +7669,7 @@ test "main" {
}
{#code_end#}
- will ouput:
+ will output:
If all {#syntax#}@compileLog{#endsyntax#} calls are removed or
@@ -7786,7 +7786,7 @@ test "main" {
the tag value is used as the enumeration value.
- If there is only one possible enum value, the resut is a {#syntax#}comptime_int{#endsyntax#}
+ If there is only one possible enum value, the result is a {#syntax#}comptime_int{#endsyntax#}
known at {#link|comptime#}.
{#see_also|@intToEnum#}
@@ -8736,7 +8736,7 @@ fn doTheTest() !void {
{#header_open|@sin#}
{#syntax#}@sin(value: anytype) @TypeOf(value){#endsyntax#}
- Sine trigometric function on a floating point number. Uses a dedicated hardware instruction
+ Sine trigonometric function on a floating point number. Uses a dedicated hardware instruction
when available.
@@ -8747,7 +8747,7 @@ fn doTheTest() !void {
{#header_open|@cos#}
{#syntax#}@cos(value: anytype) @TypeOf(value){#endsyntax#}
- Cosine trigometric function on a floating point number. Uses a dedicated hardware instruction
+ Cosine trigonometric function on a floating point number. Uses a dedicated hardware instruction
when available.
@@ -10325,7 +10325,7 @@ pub fn main() void {
Some C constructs cannot be translated to Zig - for example, goto,
structs with bitfields, and token-pasting macros. Zig employs demotion to allow translation
- to continue in the face of non-translateable entities.
+ to continue in the face of non-translatable entities.
Demotion comes in three varieties - {#link|opaque#}, extern, and
@@ -10335,13 +10335,13 @@ pub fn main() void {
Functions that contain opaque types or code constructs that cannot be translated will be demoted
to {#syntax#}extern{#endsyntax#} declarations.
- Thus, non-translateable types can still be used as pointers, and non-translateable functions
+ Thus, non-translatable types can still be used as pointers, and non-translatable functions
can be called so long as the linker is aware of the compiled function.
{#syntax#}@compileError{#endsyntax#} is used when top-level definitions (global variables,
function prototypes, macros) cannot be translated or demoted. Since Zig uses lazy analysis for
- top-level declarations, untranslateable entities will not cause a compile error in your code unless
+ top-level declarations, untranslatable entities will not cause a compile error in your code unless
you actually use them.
{#see_also|opaque|extern|@compileError#}
@@ -10353,7 +10353,7 @@ pub fn main() void {
can be translated to Zig. Macros that cannot be translated will be be demoted to
{#syntax#}@compileError{#endsyntax#}. Note that C code which uses macros will be
translated without any additional issues (since Zig operates on the pre-processed source
- with macros expanded). It is merely the macros themselves which may not be translateable to
+ with macros expanded). It is merely the macros themselves which may not be translatable to
Zig.
Consider the following example:
@@ -10373,7 +10373,7 @@ pub export fn foo() c_int {
}
pub const MAKELOCAL = @compileError("unable to translate C expr: unexpected token .Equal"); // macro.c:1:9
{#code_end#}
- Note that {#syntax#}foo{#endsyntax#} was translated correctly despite using a non-translateable
+
Note that {#syntax#}foo{#endsyntax#} was translated correctly despite using a non-translatable
macro. {#syntax#}MAKELOCAL{#endsyntax#} was demoted to {#syntax#}@compileError{#endsyntax#} since
it cannot be expressed as a Zig function; this simply means that you cannot directly use
{#syntax#}MAKELOCAL{#endsyntax#} from Zig.
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 14ad6c94a7..416584bee9 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -197,7 +197,7 @@ pub const ResultLoc = union(enum) {
none_or_ref,
/// The expression will be coerced into this type, but it will be evaluated as an rvalue.
ty: Zir.Inst.Ref,
- /// Same as `ty` but it is guaranteed that Sema will additionall perform the coercion,
+ /// Same as `ty` but it is guaranteed that Sema will additionally perform the coercion,
/// so no `as` instruction needs to be emitted.
coerced_ty: Zir.Inst.Ref,
/// The expression must store its result into this typed pointer. The result instruction
@@ -479,7 +479,7 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
return expr(gz, scope, .ref, node);
}
-/// Turn Zig AST into untyped ZIR istructions.
+/// Turn Zig AST into untyped ZIR instructions.
/// When `rl` is discard, ptr, inferred_ptr, or inferred_ptr, the
/// result instruction can be used to inspect whether it is isNoReturn() but that is it,
/// it must otherwise not be used.
diff --git a/src/Compilation.zig b/src/Compilation.zig
index 00a737c2be..ef762dae1e 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -812,7 +812,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
const needs_c_symbols = !options.skip_linker_dependencies and is_exe_or_dyn_lib;
- // WASI-only. Resolve the optinal exec-model option, defaults to command.
+ // WASI-only. Resolve the optional exec-model option, defaults to command.
const wasi_exec_model = if (options.target.os.tag != .wasi) undefined else options.wasi_exec_model orelse .command;
const comp: *Compilation = comp: {
diff --git a/src/Liveness.zig b/src/Liveness.zig
index dd0899e745..4da5eaa284 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -30,7 +30,7 @@ tomb_bits: []usize,
/// The main tomb bits are still used and the extra ones are starting with the lsb of the
/// value here.
special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
-/// Auxilliary data. The way this data is interpreted is determined contextually.
+/// Auxiliary data. The way this data is interpreted is determined contextually.
extra: []const u32,
/// Trailing is the set of instructions whose lifetimes end at the start of the then branch,
diff --git a/src/Module.zig b/src/Module.zig
index a6a9225d75..33d8f6b715 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -2188,39 +2188,39 @@ pub const LazySrcLoc = union(enum) {
node_offset_bin_op: i32,
/// The source location points to the LHS of a binary expression, found
/// by taking this AST node index offset from the containing Decl AST node,
- /// which points to a binary expression AST node. Next, nagivate to the LHS.
+ /// which points to a binary expression AST node. Next, navigate to the LHS.
/// The Decl is determined contextually.
node_offset_bin_lhs: i32,
/// The source location points to the RHS of a binary expression, found
/// by taking this AST node index offset from the containing Decl AST node,
- /// which points to a binary expression AST node. Next, nagivate to the RHS.
+ /// which points to a binary expression AST node. Next, navigate to the RHS.
/// The Decl is determined contextually.
node_offset_bin_rhs: i32,
/// The source location points to the operand of a switch expression, found
/// by taking this AST node index offset from the containing Decl AST node,
- /// which points to a switch expression AST node. Next, nagivate to the operand.
+ /// which points to a switch expression AST node. Next, navigate to the operand.
/// The Decl is determined contextually.
node_offset_switch_operand: i32,
/// The source location points to the else/`_` prong of a switch expression, found
/// by taking this AST node index offset from the containing Decl AST node,
- /// which points to a switch expression AST node. Next, nagivate to the else/`_` prong.
+ /// which points to a switch expression AST node. Next, navigate to the else/`_` prong.
/// The Decl is determined contextually.
node_offset_switch_special_prong: i32,
/// The source location points to all the ranges of a switch expression, found
/// by taking this AST node index offset from the containing Decl AST node,
- /// which points to a switch expression AST node. Next, nagivate to any of the
+ /// which points to a switch expression AST node. Next, navigate to any of the
/// range nodes. The error applies to all of them.
/// The Decl is determined contextually.
node_offset_switch_range: i32,
/// The source location points to the calling convention of a function type
/// expression, found by taking this AST node index offset from the containing
- /// Decl AST node, which points to a function type AST node. Next, nagivate to
+ /// Decl AST node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
/// The Decl is determined contextually.
node_offset_fn_type_cc: i32,
/// The source location points to the return type of a function type
/// expression, found by taking this AST node index offset from the containing
- /// Decl AST node, which points to a function type AST node. Next, nagivate to
+ /// Decl AST node, which points to a function type AST node. Next, navigate to
/// the return type node.
/// The Decl is determined contextually.
node_offset_fn_type_ret_ty: i32,
diff --git a/src/Sema.zig b/src/Sema.zig
index cc7a227ca6..2fa12baca6 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -11164,7 +11164,7 @@ fn getBuiltinType(
}
/// There is another implementation of this in `Type.onePossibleValue`. This one
-/// in `Sema` is for calling during semantic analysis, and peforms field resolution
+/// in `Sema` is for calling during semantic analysis, and performs field resolution
/// to get the answer. The one in `Type` is for calling during codegen and asserts
/// that the types are already resolved.
fn typeHasOnePossibleValue(
@@ -11541,7 +11541,7 @@ fn analyzeComptimeAlloc(
/// The places where a user can specify an address space attribute
pub const AddressSpaceContext = enum {
- /// A function is specificed to be placed in a certain address space.
+ /// A function is specified to be placed in a certain address space.
function,
/// A (global) variable is specified to be placed in a certain address space.
@@ -11553,7 +11553,7 @@ pub const AddressSpaceContext = enum {
/// In contrast to .variable, values placed in this address space are not required to be mutable.
constant,
- /// A pointer is ascripted to point into a certian address space.
+ /// A pointer is ascripted to point into a certain address space.
pointer,
};
diff --git a/src/codegen.zig b/src/codegen.zig
index 06b520c9dd..56580f91e1 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -985,7 +985,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// increasing the pc
const d_pc_p9 = @intCast(i64, delta_pc) - quant;
if (d_pc_p9 > 0) {
- // minus one becaue if its the last one, we want to leave space to change the line which is one quanta
+ // minus one because if its the last one, we want to leave space to change the line which is one quanta
try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant);
if (dbg_out.pcop_change_index.*) |pci|
dbg_out.dbg_line.items[pci] += 1;
@@ -1919,15 +1919,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
},
.shl => {
assert(!swap_lhs_and_rhs);
- const shift_amout = switch (operand) {
+ const shift_amount = switch (operand) {
.Register => |reg_op| Instruction.ShiftAmount.reg(@intToEnum(Register, reg_op.rm)),
.Immediate => |imm_op| Instruction.ShiftAmount.imm(@intCast(u5, imm_op.imm)),
};
- writeInt(u32, try self.code.addManyAsArray(4), Instruction.lsl(.al, dst_reg, op1, shift_amout).toU32());
+ writeInt(u32, try self.code.addManyAsArray(4), Instruction.lsl(.al, dst_reg, op1, shift_amount).toU32());
},
.shr => {
assert(!swap_lhs_and_rhs);
- const shift_amout = switch (operand) {
+ const shift_amount = switch (operand) {
.Register => |reg_op| Instruction.ShiftAmount.reg(@intToEnum(Register, reg_op.rm)),
.Immediate => |imm_op| Instruction.ShiftAmount.imm(@intCast(u5, imm_op.imm)),
};
@@ -1936,7 +1936,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.signed => Instruction.asr,
.unsigned => Instruction.lsr,
};
- writeInt(u32, try self.code.addManyAsArray(4), shr(.al, dst_reg, op1, shift_amout).toU32());
+ writeInt(u32, try self.code.addManyAsArray(4), shr(.al, dst_reg, op1, shift_amount).toU32());
},
else => unreachable, // not a binary instruction
}
@@ -3618,7 +3618,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
try self.blocks.putNoClobber(self.gpa, inst, .{
// A block is a setup to be able to jump to the end.
.relocs = .{},
- // It also acts as a receptical for break operands.
+ // It also acts as a receptacle for break operands.
// Here we use `MCValue.none` to represent a null value so that the first
// break instruction will choose a MCValue for the block result and overwrite
// this field. Following break instructions will use that MCValue to put their
@@ -3672,7 +3672,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail("TODO: enable larger branch offset", .{});
}
},
- else => unreachable, // attempting to perfrom an ARM relocation on a non-ARM target arch
+ else => unreachable, // attempting to perform an ARM relocation on a non-ARM target arch
}
},
}
diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig
index ec9152f96b..279ce58005 100644
--- a/src/codegen/arm.zig
+++ b/src/codegen/arm.zig
@@ -2,7 +2,7 @@ const std = @import("std");
const DW = std.dwarf;
const testing = std.testing;
-/// The condition field specifies the flags neccessary for an
+/// The condition field specifies the flags necessary for an
/// Instruction to be executed
pub const Condition = enum(u4) {
/// equal
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 650628d8c2..b2b87b97d8 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1161,7 +1161,7 @@ pub const FuncGen = struct {
/// in other instructions. This table is cleared before every function is generated.
func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Index, *const llvm.Value),
- /// These fields are used to refer to the LLVM value of the function paramaters
+ /// These fields are used to refer to the LLVM value of the function parameters
/// in an Arg instruction.
args: []*const llvm.Value,
arg_index: usize,
diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig
index f5796b06bc..5826daa5a5 100644
--- a/src/codegen/spirv.zig
+++ b/src/codegen/spirv.zig
@@ -260,7 +260,7 @@ pub const DeclGen = struct {
};
}
- /// Generate the code for `decl`. If a reportable error occured during code generation,
+ /// Generate the code for `decl`. If a reportable error occurred during code generation,
/// a message is returned by this function. Callee owns the memory. If this function
/// returns such a reportable error, it is valid to be called again for a different decl.
pub fn gen(self: *DeclGen, decl: *Decl, air: Air, liveness: Liveness) !?*Module.ErrorMsg {
@@ -565,7 +565,7 @@ pub const DeclGen = struct {
}
},
// When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.
- .Pointer => return self.fail("Cannot create pointer with unkown storage class", .{}),
+ .Pointer => return self.fail("Cannot create pointer with unknown storage class", .{}),
.Vector => {
// Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
// which work on them), so simply use those.
diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig
index bb05567236..9bd80f7d84 100644
--- a/src/codegen/wasm.zig
+++ b/src/codegen/wasm.zig
@@ -1005,7 +1005,7 @@ pub const Context = struct {
const rhs = self.resolveInst(bin_op.rhs);
// it's possible for both lhs and/or rhs to return an offset as well,
- // in which case we return the first offset occurance we find.
+ // in which case we return the first offset occurrence we find.
const offset = blk: {
if (lhs == .code_offset) break :blk lhs.code_offset;
if (rhs == .code_offset) break :blk rhs.code_offset;
@@ -1031,7 +1031,7 @@ pub const Context = struct {
const rhs = self.resolveInst(bin_op.rhs);
// it's possible for both lhs and/or rhs to return an offset as well,
- // in which case we return the first offset occurance we find.
+ // in which case we return the first offset occurrence we find.
const offset = blk: {
if (lhs == .code_offset) break :blk lhs.code_offset;
if (rhs == .code_offset) break :blk rhs.code_offset;
@@ -1395,7 +1395,7 @@ pub const Context = struct {
}
// We map every block to its block index.
- // We then determine how far we have to jump to it by substracting it from current block depth
+ // We then determine how far we have to jump to it by subtracting it from current block depth
const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?;
const writer = self.code.writer();
try writer.writeByte(wasm.opcode(.br));
diff --git a/src/link/Coff.zig b/src/link/Coff.zig
index fa234f608b..fd009ca9f8 100644
--- a/src/link/Coff.zig
+++ b/src/link/Coff.zig
@@ -50,7 +50,7 @@ last_text_block: ?*TextBlock = null,
section_table_offset: u32 = 0,
/// Section data file pointer.
section_data_offset: u32 = 0,
-/// Optiona header file pointer.
+/// Optional header file pointer.
optional_header_offset: u32 = 0,
/// Absolute virtual address of the offset table when the executable is loaded in memory.
@@ -602,7 +602,7 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void {
const current_virtual_size = mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment);
const new_virtual_size = mem.alignForwardGeneric(u32, new_raw_size, section_alignment);
// If we had to move in the virtual address space, we need to fix the VAs in the offset table, as well as the virtual address of the `.text` section
- // and the virutal size of the `.got` section
+ // and the virtual size of the `.got` section
if (new_virtual_size != current_virtual_size) {
log.debug("growing offset table from virtual size {} to {}\n", .{ current_virtual_size, new_virtual_size });
@@ -980,7 +980,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
if (self.base.options.output_mode == .Obj) {
- // LLD's COFF driver does not support the equvialent of `-r` so we do a simple file copy
+ // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy
// here. TODO: think carefully about how we can avoid this redundant operation when doing
// build-obj. See also the corresponding TODO in linkAsArchive.
const the_object_path = blk: {
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
index e69b85ca7f..dc44474c0a 100644
--- a/src/link/MachO.zig
+++ b/src/link/MachO.zig
@@ -213,7 +213,7 @@ decls: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{},
/// Currently active Module.Decl.
/// TODO this might not be necessary if we figure out how to pass Module.Decl instance
-/// to codegen.genSetReg() or alterntively move PIE displacement for MCValue{ .memory = x }
+/// to codegen.genSetReg() or alternatively move PIE displacement for MCValue{ .memory = x }
/// somewhere else in the codegen.
active_decl: ?*Module.Decl = null,
@@ -512,7 +512,7 @@ pub fn flush(self: *MachO, comp: *Compilation) !void {
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
if (self.base.options.output_mode == .Obj) {
- // LLD's MachO driver does not support the equvialent of `-r` so we do a simple file copy
+ // LLD's MachO driver does not support the equivalent of `-r` so we do a simple file copy
// here. TODO: think carefully about how we can avoid this redundant operation when doing
// build-obj. See also the corresponding TODO in linkAsArchive.
const the_object_path = blk: {
@@ -2245,7 +2245,7 @@ pub fn createStubAtom(self: *MachO, laptr_sym_index: u32) !*Atom {
fn createTentativeDefAtoms(self: *MachO) !void {
if (self.tentatives.count() == 0) return;
// Convert any tentative definition into a regular symbol and allocate
- // text blocks for each tentative defintion.
+ // text blocks for each tentative definition.
while (self.tentatives.popOrNull()) |entry| {
const match = MatchingSection{
.seg = self.data_segment_cmd_index.?,
@@ -4609,7 +4609,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void {
// Because we insert lazy binding opcodes in reverse order (from last to the first atom),
// we need reverse the order of atom traversal here as well.
- // TODO figure out a less error prone mechanims for this!
+ // TODO figure out a less error prone mechanisms for this!
var atom = last_atom;
while (atom.prev) |prev| {
atom = prev;
diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig
index bb6730fe0f..46e5191ab3 100644
--- a/src/link/MachO/Atom.zig
+++ b/src/link/MachO/Atom.zig
@@ -41,7 +41,7 @@ code: std.ArrayListUnmanaged(u8) = .{},
size: u64,
/// Alignment of this atom as a power of 2.
-/// For instance, aligmment of 0 should be read as 2^0 = 1 byte aligned.
+/// For instance, alignment of 0 should be read as 2^0 = 1 byte aligned.
alignment: u32,
/// List of relocations belonging to this atom.
diff --git a/src/link/MachO/Trie.zig b/src/link/MachO/Trie.zig
index 7bf451f2c8..c166aaf432 100644
--- a/src/link/MachO/Trie.zig
+++ b/src/link/MachO/Trie.zig
@@ -506,7 +506,7 @@ test "write Trie to a byte stream" {
});
try trie.finalize(gpa);
- try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
+ try trie.finalize(gpa); // Finalizing multiple times is a nop subsequently unless we add new nodes.
const exp_buffer = [_]u8{
0x0, 0x1, // node root
diff --git a/src/link/Plan9/aout.zig b/src/link/Plan9/aout.zig
index f3570f880c..39994516fa 100644
--- a/src/link/Plan9/aout.zig
+++ b/src/link/Plan9/aout.zig
@@ -16,7 +16,7 @@ pub const ExecHdr = extern struct {
comptime {
assert(@sizeOf(@This()) == 32);
}
- /// It is up to the caller to disgard the last 8 bytes if the header is not fat.
+ /// It is up to the caller to discard the last 8 bytes if the header is not fat.
pub fn toU8s(self: *@This()) [40]u8 {
var buf: [40]u8 = undefined;
var i: u8 = 0;
diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig
index 17b656a06c..f9d3f7a1e6 100644
--- a/src/link/SpirV.zig
+++ b/src/link/SpirV.zig
@@ -12,7 +12,7 @@
//! - OpName and OpMemberName instructions.
//! - OpModuleProcessed instructions.
//! All annotation (decoration) instructions.
-//! All type declaration instructions, constant instructions, global variable declarations, (preferrably) OpUndef instructions.
+//! All type declaration instructions, constant instructions, global variable declarations, (preferably) OpUndef instructions.
//! All function declarations without a body (extern functions presumably).
//! All regular functions.
@@ -93,7 +93,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
if (options.use_llvm) return error.LLVM_BackendIsTODO_ForSpirV; // TODO: LLVM Doesn't support SpirV at all.
if (options.use_lld) return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all.
- // TODO: read the file and keep vaild parts instead of truncating
+ // TODO: read the file and keep valid parts instead of truncating
const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
errdefer file.close();
diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig
index 3de1fb49cc..a75ad1b2f7 100644
--- a/src/link/Wasm.zig
+++ b/src/link/Wasm.zig
@@ -35,7 +35,7 @@ llvm_object: ?*LlvmObject = null,
/// TODO: can/should we access some data structure in Module directly?
funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
/// List of all extern function Decls to be written to the `import` section of the
-/// wasm binary. The positin in the list defines the function index
+/// wasm binary. The position in the list defines the function index
ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
/// When importing objects from the host environment, a name must be supplied.
/// LLVM uses "env" by default when none is given. This would be a good default for Zig
@@ -714,7 +714,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
if (self.base.options.output_mode == .Obj) {
- // LLD's WASM driver does not support the equvialent of `-r` so we do a simple file copy
+ // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy
// here. TODO: think carefully about how we can avoid this redundant operation when doing
// build-obj. See also the corresponding TODO in linkAsArchive.
const the_object_path = blk: {
@@ -756,7 +756,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
if (self.base.options.output_mode == .Exe) {
// Increase the default stack size to a more reasonable value of 1MB instead of
- // the default of 1 Wasm page being 64KB, unless overriden by the user.
+ // the default of 1 Wasm page being 64KB, unless overridden by the user.
try argv.append("-z");
const stack_size = self.base.options.stack_size_override orelse 1048576;
const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp
index 320d8ff9b2..ec4ff8fc9e 100644
--- a/src/stage1/analyze.cpp
+++ b/src/stage1/analyze.cpp
@@ -6804,7 +6804,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
// Since this frame is async, an await might represent a suspend point, and
// therefore need to spill. It also needs to mark expr scopes as having to spill.
// For example: foo() + await z
- // The funtion call result of foo() must be spilled.
+ // The function call result of foo() must be spilled.
for (size_t i = 0; i < fn->await_list.length; i += 1) {
Stage1AirInstAwait *await = fn->await_list.at(i);
if (await->is_nosuspend) {
diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp
index 87dfee1bf2..b853961beb 100644
--- a/src/stage1/ir.cpp
+++ b/src/stage1/ir.cpp
@@ -6374,7 +6374,7 @@ static Stage1AirInst *ir_analyze_enum_to_union(IrAnalyze *ira, Scope *scope, Ast
if (target->value->type->data.enumeration.non_exhaustive) {
ir_add_error_node(ira, source_node,
- buf_sprintf("runtime cast to union '%s' from non-exhustive enum",
+ buf_sprintf("runtime cast to union '%s' from non-exhaustive enum",
buf_ptr(&wanted_type->name)));
return ira->codegen->invalid_inst_gen;
}
@@ -15189,7 +15189,7 @@ static Stage1AirInst *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
return ir_analyze_inferred_field_ptr(ira, field_name, scope, source_node, container_ptr, bare_type);
}
- // Tracks wether we should return an undefined value of the correct type.
+ // Tracks whether we should return an undefined value of the correct type.
// We do this if the container pointer is undefined and we are in a TypeOf call.
bool return_undef = container_ptr->value->special == ConstValSpecialUndef && \
get_scope_typeof(scope) != nullptr;
@@ -15248,7 +15248,7 @@ static Stage1AirInst *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_
if (type_is_invalid(union_val->type))
return ira->codegen->invalid_inst_gen;
- // Reject undefined values unless we're intializing the union:
+ // Reject undefined values unless we're initializing the union:
// a undefined union means also the tag is undefined, accessing
// its payload slot is UB.
const UndefAllowed allow_undef = initializing ? UndefOk : UndefBad;
diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp
index 152221926d..a76d3e4d5a 100644
--- a/src/stage1/ir_print.cpp
+++ b/src/stage1/ir_print.cpp
@@ -558,7 +558,7 @@ const char* ir_inst_gen_type_str(Stage1AirInstId id) {
case Stage1AirInstIdWasmMemoryGrow:
return "GenWasmMemoryGrow";
case Stage1AirInstIdExtern:
- return "GenExtrern";
+ return "GenExtern";
}
zig_unreachable();
}
@@ -829,7 +829,7 @@ static const char *cast_op_str(CastOp op) {
case CastOpIntToFloat: return "IntToFloat";
case CastOpFloatToInt: return "FloatToInt";
case CastOpBoolToInt: return "BoolToInt";
- case CastOpNumLitToConcrete: return "NumLitToConcrate";
+ case CastOpNumLitToConcrete: return "NumLitToConcrete";
case CastOpErrSet: return "ErrSet";
case CastOpBitCast: return "BitCast";
}
diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp
index b06a944172..f7061bb232 100644
--- a/src/stage1/parser.cpp
+++ b/src/stage1/parser.cpp
@@ -2073,7 +2073,7 @@ static AstNode *ast_parse_field_init(ParseContext *pc) {
return nullptr;
}
if (eat_token_if(pc, TokenIdEq) == 0) {
- // Because ".Name" can also be intepreted as an enum literal, we should put back
+ // Because ".Name" can also be interpreted as an enum literal, we should put back
// those two tokens again so that the parser can try to parse them as the enum
// literal later.
put_back_token(pc);
diff --git a/src/type.zig b/src/type.zig
index f2fe9e4ccb..4a0f2a0536 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -3419,7 +3419,7 @@ pub const Type = extern union {
anyerror_void_error_union,
generic_poison,
/// This is a special type for variadic parameters of a function call.
- /// Casts to it will validate that the type can be passed to a c calling convetion function.
+ /// Casts to it will validate that the type can be passed to a c calling convention function.
var_args_param,
/// Same as `empty_struct` except it has an empty namespace.
empty_struct_literal,
diff --git a/src/windows_com.hpp b/src/windows_com.hpp
index f9833e0912..5f0f5565f7 100644
--- a/src/windows_com.hpp
+++ b/src/windows_com.hpp
@@ -352,7 +352,7 @@ extern "C" {
///
/// Gets product-specific properties.
///
- /// A pointer to an instance of . This may be NULL if no properties are defined.
+ /// A pointer to an instance of . This may be NULL if no properties are defined.
/// Standard HRESULT indicating success or failure, including E_FILENOTFOUND if the instance state does not exist.
STDMETHOD(GetProperties)(
_Outptr_result_maybenull_ ISetupPropertyStore** ppProperties
diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp
index 6611585f68..685ee8bcce 100644
--- a/src/zig_clang.cpp
+++ b/src/zig_clang.cpp
@@ -1537,10 +1537,10 @@ void ZigClang_detect_enum_ConstantExprKind(clang::Expr::ConstantExprKind x) {
break;
}
}
-static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_Normal == clang::Expr::ConstantExprKind::Normal, "");
-static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_NonClassTemplateArgument == clang::Expr::ConstantExprKind::NonClassTemplateArgument, "");
-static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_ClassTemplateArgument == clang::Expr::ConstantExprKind::ClassTemplateArgument, "");
-static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_ImmediateInvocation == clang::Expr::ConstantExprKind::ImmediateInvocation, "");
+static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_Normal == clang::Expr::ConstantExprKind::Normal, "");
+static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_NonClassTemplateArgument == clang::Expr::ConstantExprKind::NonClassTemplateArgument, "");
+static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_ClassTemplateArgument == clang::Expr::ConstantExprKind::ClassTemplateArgument, "");
+static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_ImmediateInvocation == clang::Expr::ConstantExprKind::ImmediateInvocation, "");
static_assert(sizeof(ZigClangAPValue) == sizeof(clang::APValue), "");
diff --git a/src/zig_clang.h b/src/zig_clang.h
index 0e7a8b2990..26b4b3ca9a 100644
--- a/src/zig_clang.h
+++ b/src/zig_clang.h
@@ -934,10 +934,10 @@ enum ZigClangPreprocessedEntity_EntityKind {
};
enum ZigClangExpr_ConstantExprKind {
- ZigClangExpr_ContantExprKind_Normal,
- ZigClangExpr_ContantExprKind_NonClassTemplateArgument,
- ZigClangExpr_ContantExprKind_ClassTemplateArgument,
- ZigClangExpr_ContantExprKind_ImmediateInvocation,
+ ZigClangExpr_ConstantExprKind_Normal,
+ ZigClangExpr_ConstantExprKind_NonClassTemplateArgument,
+ ZigClangExpr_ConstantExprKind_ClassTemplateArgument,
+ ZigClangExpr_ConstantExprKind_ImmediateInvocation,
};
enum ZigClangUnaryExprOrTypeTrait_Kind {
diff --git a/src/zig_llvm-ar.cpp b/src/zig_llvm-ar.cpp
index e4c376adbc..107f500fcb 100644
--- a/src/zig_llvm-ar.cpp
+++ b/src/zig_llvm-ar.cpp
@@ -492,7 +492,7 @@ static std::string ArchiveName;
static std::vector> ArchiveBuffers;
static std::vector> Archives;
-// This variable holds the list of member files to proecess, as given
+// This variable holds the list of member files to process, as given
// on the command line.
static std::vector Members;
diff --git a/test/behavior/array_stage1.zig b/test/behavior/array_stage1.zig
index d3a97665ac..c290ef9a08 100644
--- a/test/behavior/array_stage1.zig
+++ b/test/behavior/array_stage1.zig
@@ -140,7 +140,7 @@ fn testArrayByValAtComptime(b: [2]u8) u8 {
return b[0];
}
-test "comptime evalutating function that takes array by value" {
+test "comptime evaluating function that takes array by value" {
const arr = [_]u8{ 0, 1 };
_ = comptime testArrayByValAtComptime(arr);
_ = comptime testArrayByValAtComptime(arr);
diff --git a/test/behavior/bugs/1735.zig b/test/behavior/bugs/1735.zig
index f3aa6eb9ec..1f6e3c99f4 100644
--- a/test/behavior/bugs/1735.zig
+++ b/test/behavior/bugs/1735.zig
@@ -40,7 +40,7 @@ const a = struct {
}
};
-test "intialization" {
+test "initialization" {
var t = a.init();
try std.testing.expect(t.foo.len == 0);
}
diff --git a/test/behavior/eval_stage1.zig b/test/behavior/eval_stage1.zig
index 644de50fd0..743f7af69e 100644
--- a/test/behavior/eval_stage1.zig
+++ b/test/behavior/eval_stage1.zig
@@ -88,7 +88,7 @@ var st_init_str_foo = StInitStrFoo{
.y = true,
};
-test "statically initalized array literal" {
+test "statically initialized array literal" {
const y: [4]u8 = st_init_arr_lit_x;
try expect(y[3] == 4);
}
diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig
index e512565b80..62afc74d83 100644
--- a/test/behavior/switch.zig
+++ b/test/behavior/switch.zig
@@ -65,18 +65,18 @@ fn nonConstSwitchOnEnum(fruit: Fruit) void {
}
test "switch statement" {
- try nonConstSwitch(SwitchStatmentFoo.C);
+ try nonConstSwitch(SwitchStatementFoo.C);
}
-fn nonConstSwitch(foo: SwitchStatmentFoo) !void {
+fn nonConstSwitch(foo: SwitchStatementFoo) !void {
const val = switch (foo) {
- SwitchStatmentFoo.A => @as(i32, 1),
- SwitchStatmentFoo.B => 2,
- SwitchStatmentFoo.C => 3,
- SwitchStatmentFoo.D => 4,
+ SwitchStatementFoo.A => @as(i32, 1),
+ SwitchStatementFoo.B => 2,
+ SwitchStatementFoo.C => 3,
+ SwitchStatementFoo.D => 4,
};
try expect(val == 3);
}
-const SwitchStatmentFoo = enum {
+const SwitchStatementFoo = enum {
A,
B,
C,
diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig
index 2c615b542b..b673542f8b 100644
--- a/test/behavior/vector.zig
+++ b/test/behavior/vector.zig
@@ -116,7 +116,7 @@ test "array to vector" {
_ = vec;
}
-test "vector casts of sizes not divisable by 8" {
+test "vector casts of sizes not divisible by 8" {
const S = struct {
fn doTheTest() !void {
{
diff --git a/test/compile_errors.zig b/test/compile_errors.zig
index 058fd1f2db..0e59b0523f 100644
--- a/test/compile_errors.zig
+++ b/test/compile_errors.zig
@@ -683,7 +683,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\ _ = u;
\\}
, &[_][]const u8{
- "tmp.zig:12:16: error: runtime cast to union 'U' from non-exhustive enum",
+ "tmp.zig:12:16: error: runtime cast to union 'U' from non-exhaustive enum",
"tmp.zig:17:16: error: no tag by value 15",
});
@@ -6145,9 +6145,9 @@ pub fn addCases(ctx: *TestContext) !void {
});
ctx.objErrStage1("endless loop in function evaluation",
- \\const seventh_fib_number = fibbonaci(7);
- \\fn fibbonaci(x: i32) i32 {
- \\ return fibbonaci(x - 1) + fibbonaci(x - 2);
+ \\const seventh_fib_number = fibonacci(7);
+ \\fn fibonacci(x: i32) i32 {
+ \\ return fibonacci(x - 1) + fibonacci(x - 2);
\\}
\\
\\export fn entry() usize { return @sizeOf(@TypeOf(seventh_fib_number)); }
@@ -6775,7 +6775,7 @@ pub fn addCases(ctx: *TestContext) !void {
"tmp.zig:2:5: error: expression value is ignored",
});
- ctx.objErrStage1("ignored defered statement value",
+ ctx.objErrStage1("ignored deferred statement value",
\\export fn foo() void {
\\ defer {1;}
\\}
@@ -6783,7 +6783,7 @@ pub fn addCases(ctx: *TestContext) !void {
"tmp.zig:2:12: error: expression value is ignored",
});
- ctx.objErrStage1("ignored defered function call",
+ ctx.objErrStage1("ignored deferred function call",
\\export fn foo() void {
\\ defer bar();
\\}
diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig
index 28ba7aa704..c222a00eb7 100644
--- a/test/run_translated_c.zig
+++ b/test/run_translated_c.zig
@@ -24,7 +24,7 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
\\}
, "DEG2RAD is: 0.017453" ++ nl);
- cases.add("use global scope for record/enum/typedef type transalation if needed",
+ cases.add("use global scope for record/enum/typedef type translation if needed",
\\void bar(void);
\\void baz(void);
\\struct foo { int x; };
@@ -394,7 +394,7 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
\\}
, "");
- cases.add("ensure array casts outisde +=",
+ cases.add("ensure array casts outside +=",
\\#include
\\static int hash_binary(int k)
\\{
diff --git a/test/stage2/darwin.zig b/test/stage2/darwin.zig
index 90058404d9..84334828bd 100644
--- a/test/stage2/darwin.zig
+++ b/test/stage2/darwin.zig
@@ -118,7 +118,7 @@ pub fn addCases(ctx: *TestContext) !void {
{
var case = ctx.exe("corner case - update existing, singular TextBlock", target);
- // This test case also covers an infrequent scenarion where the string table *may* be relocated
+ // This test case also covers an infrequent scenario where the string table *may* be relocated
// into the position preceeding the symbol table which results in a dyld error.
case.addCompareOutput(
\\extern fn exit(usize) noreturn;
diff --git a/test/tests.zig b/test/tests.zig
index a90531c600..0577092845 100644
--- a/test/tests.zig
+++ b/test/tests.zig
@@ -455,7 +455,7 @@ pub fn addTranslateCTests(b: *build.Builder, test_filter: ?[]const u8) *build.St
const cases = b.allocator.create(TranslateCContext) catch unreachable;
cases.* = TranslateCContext{
.b = b,
- .step = b.step("test-translate-c", "Run the C transation tests"),
+ .step = b.step("test-translate-c", "Run the C translation tests"),
.test_index = 0,
.test_filter = test_filter,
};
diff --git a/test/translate_c.zig b/test/translate_c.zig
index 5ff6c22ead..db665124ba 100644
--- a/test/translate_c.zig
+++ b/test/translate_c.zig
@@ -3188,7 +3188,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
\\}
});
- cases.add("macro comparisions",
+ cases.add("macro comparisons",
\\#define MIN(a, b) ((b) < (a) ? (b) : (a))
\\#define MAX(a, b) ((b) > (a) ? (b) : (a))
, &[_][]const u8{
@@ -3443,7 +3443,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
});
}
- cases.add("unnamed fields have predictabile names",
+ cases.add("unnamed fields have predictable names",
\\struct a {
\\ struct {};
\\};
From 8f58e2d77951cdb046e365394c5f02c9b3a93a4f Mon Sep 17 00:00:00 2001
From: joachimschmidt557
Date: Thu, 23 Sep 2021 22:47:12 +0200
Subject: [PATCH 112/160] stage2 codegen: move bit definitions to src/arch
---
CMakeLists.txt | 8 ++--
.../aarch64.zig => arch/aarch64/bits.zig} | 0
src/{codegen/arm.zig => arch/arm/bits.zig} | 0
.../riscv64.zig => arch/riscv64/bits.zig} | 0
src/{codegen/x86.zig => arch/x86/bits.zig} | 0
.../x86_64.zig => arch/x86_64/bits.zig} | 0
src/codegen.zig | 48 +++++++++----------
src/link/MachO.zig | 4 +-
src/link/MachO/Atom.zig | 2 +-
9 files changed, 31 insertions(+), 31 deletions(-)
rename src/{codegen/aarch64.zig => arch/aarch64/bits.zig} (100%)
rename src/{codegen/arm.zig => arch/arm/bits.zig} (100%)
rename src/{codegen/riscv64.zig => arch/riscv64/bits.zig} (100%)
rename src/{codegen/x86.zig => arch/x86/bits.zig} (100%)
rename src/{codegen/x86_64.zig => arch/x86_64/bits.zig} (100%)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 42c0b7e0da..2839a3cffc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -551,18 +551,18 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
"${CMAKE_SOURCE_DIR}/src/WaitGroup.zig"
"${CMAKE_SOURCE_DIR}/src/Zir.zig"
+ "${CMAKE_SOURCE_DIR}/src/arch/aarch64/bits.zig"
+ "${CMAKE_SOURCE_DIR}/src/arch/arm/bits.zig"
+ "${CMAKE_SOURCE_DIR}/src/arch/riscv64/bits.zig"
+ "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig"
"${CMAKE_SOURCE_DIR}/src/clang.zig"
"${CMAKE_SOURCE_DIR}/src/clang_options.zig"
"${CMAKE_SOURCE_DIR}/src/clang_options_data.zig"
"${CMAKE_SOURCE_DIR}/src/codegen.zig"
- "${CMAKE_SOURCE_DIR}/src/codegen/aarch64.zig"
- "${CMAKE_SOURCE_DIR}/src/codegen/arm.zig"
"${CMAKE_SOURCE_DIR}/src/codegen/c.zig"
"${CMAKE_SOURCE_DIR}/src/codegen/llvm.zig"
"${CMAKE_SOURCE_DIR}/src/codegen/llvm/bindings.zig"
- "${CMAKE_SOURCE_DIR}/src/codegen/riscv64.zig"
"${CMAKE_SOURCE_DIR}/src/codegen/wasm.zig"
- "${CMAKE_SOURCE_DIR}/src/codegen/x86_64.zig"
"${CMAKE_SOURCE_DIR}/src/glibc.zig"
"${CMAKE_SOURCE_DIR}/src/introspect.zig"
"${CMAKE_SOURCE_DIR}/src/libc_installation.zig"
diff --git a/src/codegen/aarch64.zig b/src/arch/aarch64/bits.zig
similarity index 100%
rename from src/codegen/aarch64.zig
rename to src/arch/aarch64/bits.zig
diff --git a/src/codegen/arm.zig b/src/arch/arm/bits.zig
similarity index 100%
rename from src/codegen/arm.zig
rename to src/arch/arm/bits.zig
diff --git a/src/codegen/riscv64.zig b/src/arch/riscv64/bits.zig
similarity index 100%
rename from src/codegen/riscv64.zig
rename to src/arch/riscv64/bits.zig
diff --git a/src/codegen/x86.zig b/src/arch/x86/bits.zig
similarity index 100%
rename from src/codegen/x86.zig
rename to src/arch/x86/bits.zig
diff --git a/src/codegen/x86_64.zig b/src/arch/x86_64/bits.zig
similarity index 100%
rename from src/codegen/x86_64.zig
rename to src/arch/x86_64/bits.zig
diff --git a/src/codegen.zig b/src/codegen.zig
index 56580f91e1..f812cbc5d4 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -21,7 +21,7 @@ const log = std.log.scoped(.codegen);
const build_options = @import("build_options");
const RegisterManager = @import("register_manager.zig").RegisterManager;
-const X8664Encoder = @import("codegen/x86_64.zig").Encoder;
+const X8664Encoder = @import("arch/x86_64/bits.zig").Encoder;
pub const FnResult = union(enum) {
/// The `code` parameter passed to `generateSymbol` has the value appended.
@@ -470,7 +470,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
/// A branch in the ARM instruction set
arm_branch: struct {
pos: usize,
- cond: @import("codegen/arm.zig").Condition,
+ cond: @import("arch/arm/bits.zig").Condition,
},
};
@@ -5336,11 +5336,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}
const Register = switch (arch) {
- .i386 => @import("codegen/x86.zig").Register,
- .x86_64 => @import("codegen/x86_64.zig").Register,
- .riscv64 => @import("codegen/riscv64.zig").Register,
- .arm, .armeb => @import("codegen/arm.zig").Register,
- .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").Register,
+ .i386 => @import("arch/x86/bits.zig").Register,
+ .x86_64 => @import("arch/x86_64/bits.zig").Register,
+ .riscv64 => @import("arch/riscv64/bits.zig").Register,
+ .arm, .armeb => @import("arch/arm/bits.zig").Register,
+ .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").Register,
else => enum {
dummy,
@@ -5352,39 +5352,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
};
const Instruction = switch (arch) {
- .riscv64 => @import("codegen/riscv64.zig").Instruction,
- .arm, .armeb => @import("codegen/arm.zig").Instruction,
- .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").Instruction,
+ .riscv64 => @import("arch/riscv64/bits.zig").Instruction,
+ .arm, .armeb => @import("arch/arm/bits.zig").Instruction,
+ .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").Instruction,
else => void,
};
const Condition = switch (arch) {
- .arm, .armeb => @import("codegen/arm.zig").Condition,
+ .arm, .armeb => @import("arch/arm/bits.zig").Condition,
else => void,
};
const callee_preserved_regs = switch (arch) {
- .i386 => @import("codegen/x86.zig").callee_preserved_regs,
- .x86_64 => @import("codegen/x86_64.zig").callee_preserved_regs,
- .riscv64 => @import("codegen/riscv64.zig").callee_preserved_regs,
- .arm, .armeb => @import("codegen/arm.zig").callee_preserved_regs,
- .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").callee_preserved_regs,
+ .i386 => @import("arch/x86/bits.zig").callee_preserved_regs,
+ .x86_64 => @import("arch/x86_64/bits.zig").callee_preserved_regs,
+ .riscv64 => @import("arch/riscv64/bits.zig").callee_preserved_regs,
+ .arm, .armeb => @import("arch/arm/bits.zig").callee_preserved_regs,
+ .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").callee_preserved_regs,
else => [_]Register{},
};
const c_abi_int_param_regs = switch (arch) {
- .i386 => @import("codegen/x86.zig").c_abi_int_param_regs,
- .x86_64 => @import("codegen/x86_64.zig").c_abi_int_param_regs,
- .arm, .armeb => @import("codegen/arm.zig").c_abi_int_param_regs,
- .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").c_abi_int_param_regs,
+ .i386 => @import("arch/x86/bits.zig").c_abi_int_param_regs,
+ .x86_64 => @import("arch/x86_64/bits.zig").c_abi_int_param_regs,
+ .arm, .armeb => @import("arch/arm/bits.zig").c_abi_int_param_regs,
+ .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").c_abi_int_param_regs,
else => [_]Register{},
};
const c_abi_int_return_regs = switch (arch) {
- .i386 => @import("codegen/x86.zig").c_abi_int_return_regs,
- .x86_64 => @import("codegen/x86_64.zig").c_abi_int_return_regs,
- .arm, .armeb => @import("codegen/arm.zig").c_abi_int_return_regs,
- .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").c_abi_int_return_regs,
+ .i386 => @import("arch/x86/bits.zig").c_abi_int_return_regs,
+ .x86_64 => @import("arch/x86_64/bits.zig").c_abi_int_return_regs,
+ .arm, .armeb => @import("arch/arm/bits.zig").c_abi_int_return_regs,
+ .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").c_abi_int_return_regs,
else => [_]Register{},
};
diff --git a/src/link/MachO.zig b/src/link/MachO.zig
index dc44474c0a..9a2c462d55 100644
--- a/src/link/MachO.zig
+++ b/src/link/MachO.zig
@@ -12,7 +12,7 @@ const math = std.math;
const mem = std.mem;
const meta = std.meta;
-const aarch64 = @import("../codegen/aarch64.zig");
+const aarch64 = @import("../arch/aarch64/bits.zig");
const bind = @import("MachO/bind.zig");
const codegen = @import("../codegen.zig");
const commands = @import("MachO/commands.zig");
@@ -200,7 +200,7 @@ atoms: std.AutoHashMapUnmanaged(MatchingSection, *Atom) = .{},
/// List of atoms that are owned directly by the linker.
/// Currently these are only atoms that are the result of linking
-/// object files. Atoms which take part in incremental linking are
+/// object files. Atoms which take part in incremental linking are
/// at present owned by Module.Decl.
/// TODO consolidate this.
managed_atoms: std.ArrayListUnmanaged(*Atom) = .{},
diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig
index 46e5191ab3..a98f624176 100644
--- a/src/link/MachO/Atom.zig
+++ b/src/link/MachO/Atom.zig
@@ -2,7 +2,7 @@ const Atom = @This();
const std = @import("std");
const build_options = @import("build_options");
-const aarch64 = @import("../../codegen/aarch64.zig");
+const aarch64 = @import("../../arch/aarch64/bits.zig");
const assert = std.debug.assert;
const commands = @import("commands.zig");
const log = std.log.scoped(.text_block);
From 1e7009a9d982faa466517063452ed7d299b66966 Mon Sep 17 00:00:00 2001
From: Martin Wickham
Date: Thu, 23 Sep 2021 18:09:35 -0500
Subject: [PATCH 113/160] Fix error references across inline and comptime
functions
---
src/Module.zig | 48 ++++++++++++++++++++++--------------------------
src/Sema.zig | 4 ++--
2 files changed, 24 insertions(+), 28 deletions(-)
diff --git a/src/Module.zig b/src/Module.zig
index 33d8f6b715..278f8621d8 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -248,6 +248,9 @@ pub const Export = struct {
link: link.File.Export,
/// The Decl that performs the export. Note that this is *not* the Decl being exported.
owner_decl: *Decl,
+ /// The Decl containing the export statement. Inline function calls
+ /// may cause this to be different from the owner_decl.
+ src_decl: *Decl,
/// The Decl being exported. Note this is *not* the Decl performing the export.
exported_decl: *Decl,
status: enum {
@@ -261,8 +264,8 @@ pub const Export = struct {
pub fn getSrcLoc(exp: Export) SrcLoc {
return .{
- .file_scope = exp.owner_decl.namespace.file_scope,
- .parent_decl_node = exp.owner_decl.src_node,
+ .file_scope = exp.src_decl.namespace.file_scope,
+ .parent_decl_node = exp.src_decl.src_node,
.lazy = exp.src,
};
}
@@ -1014,15 +1017,6 @@ pub const Scope = struct {
return @fieldParentPtr(T, "base", base);
}
- /// Get the decl that is currently being analyzed
- pub fn ownerDecl(scope: *Scope) ?*Decl {
- return switch (scope.tag) {
- .block => scope.cast(Block).?.sema.owner_decl,
- .file => null,
- .namespace => null,
- };
- }
-
/// Get the decl which contains this decl, for the purposes of source reporting
pub fn srcDecl(scope: *Scope) ?*Decl {
return switch (scope.tag) {
@@ -3402,7 +3396,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
}
// The scope needs to have the decl in it.
const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) };
- try mod.analyzeExport(&block_scope.base, export_src, options, decl);
+ try mod.analyzeExport(&block_scope, export_src, options, decl);
}
return type_changed or is_inline != prev_is_inline;
}
@@ -3462,7 +3456,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
const export_src = src; // TODO point to the export token
// The scope needs to have the decl in it.
const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) };
- try mod.analyzeExport(&block_scope.base, export_src, options, decl);
+ try mod.analyzeExport(&block_scope, export_src, options, decl);
}
return type_changed;
@@ -3931,7 +3925,7 @@ pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void {
pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void {
log.debug("deleteAnonDecl {*} ({s})", .{ decl, decl.name });
- const scope_decl = scope.ownerDecl().?;
+ const scope_decl = scope.srcDecl().?;
assert(scope_decl.namespace.anon_decls.swapRemove(decl));
decl.destroy(mod);
}
@@ -4209,7 +4203,7 @@ pub fn getErrorValue(mod: *Module, name: []const u8) !std.StringHashMapUnmanaged
pub fn analyzeExport(
mod: *Module,
- scope: *Scope,
+ block: *Scope.Block,
src: LazySrcLoc,
borrowed_options: std.builtin.ExportOptions,
exported_decl: *Decl,
@@ -4217,7 +4211,7 @@ pub fn analyzeExport(
try mod.ensureDeclAnalyzed(exported_decl);
switch (exported_decl.ty.zigTypeTag()) {
.Fn => {},
- else => return mod.fail(scope, src, "unable to export type '{}'", .{exported_decl.ty}),
+ else => return mod.fail(&block.base, src, "unable to export type '{}'", .{exported_decl.ty}),
}
const gpa = mod.gpa;
@@ -4234,7 +4228,8 @@ pub fn analyzeExport(
const section: ?[]const u8 = if (borrowed_options.section) |s| try gpa.dupe(u8, s) else null;
errdefer if (section) |s| gpa.free(s);
- const owner_decl = scope.ownerDecl().?;
+ const src_decl = block.src_decl;
+ const owner_decl = block.sema.owner_decl;
log.debug("exporting Decl '{s}' as symbol '{s}' from Decl '{s}'", .{
exported_decl.name, symbol_name, owner_decl.name,
@@ -4257,6 +4252,7 @@ pub fn analyzeExport(
.spirv => .{ .spirv = {} },
},
.owner_decl = owner_decl,
+ .src_decl = src_decl,
.exported_decl = exported_decl,
.status = .in_progress,
};
@@ -4287,38 +4283,38 @@ pub fn createAnonymousDeclNamed(
typed_value: TypedValue,
name: [:0]u8,
) !*Decl {
- return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, scope.srcScope(), typed_value, name);
+ return mod.createAnonymousDeclFromDeclNamed(scope.srcDecl().?, scope.srcScope(), typed_value, name);
}
pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl {
- return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, scope.srcScope(), typed_value);
+ return mod.createAnonymousDeclFromDecl(scope.srcDecl().?, scope.srcScope(), typed_value);
}
-pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, src_scope: ?*CaptureScope, tv: TypedValue) !*Decl {
+pub fn createAnonymousDeclFromDecl(mod: *Module, src_decl: *Decl, src_scope: ?*CaptureScope, tv: TypedValue) !*Decl {
const name_index = mod.getNextAnonNameIndex();
const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{
- owner_decl.name, name_index,
+ src_decl.name, name_index,
});
- return mod.createAnonymousDeclFromDeclNamed(owner_decl, src_scope, tv, name);
+ return mod.createAnonymousDeclFromDeclNamed(src_decl, src_scope, tv, name);
}
/// Takes ownership of `name` even if it returns an error.
pub fn createAnonymousDeclFromDeclNamed(
mod: *Module,
- owner_decl: *Decl,
+ src_decl: *Decl,
src_scope: ?*CaptureScope,
typed_value: TypedValue,
name: [:0]u8,
) !*Decl {
errdefer mod.gpa.free(name);
- const namespace = owner_decl.namespace;
+ const namespace = src_decl.namespace;
try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1);
- const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node, src_scope);
+ const new_decl = try mod.allocateNewDecl(namespace, src_decl.src_node, src_scope);
new_decl.name = name;
- new_decl.src_line = owner_decl.src_line;
+ new_decl.src_line = src_decl.src_line;
new_decl.ty = typed_value.ty;
new_decl.val = typed_value.val;
new_decl.align_val = Value.initTag(.null_value);
diff --git a/src/Sema.zig b/src/Sema.zig
index 2fa12baca6..87cde2ca1a 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -2447,7 +2447,7 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro
}
const decl = try sema.lookupIdentifier(block, operand_src, decl_name);
const options = try sema.resolveExportOptions(block, options_src, extra.options);
- try sema.mod.analyzeExport(&block.base, src, options, decl);
+ try sema.mod.analyzeExport(block, src, options, decl);
}
fn zirExportValue(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
@@ -2465,7 +2465,7 @@ fn zirExportValue(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compil
.function => operand.val.castTag(.function).?.data.owner_decl,
else => return sema.mod.fail(&block.base, operand_src, "TODO implement exporting arbitrary Value objects", .{}), // TODO put this Value into an anonymous Decl and then export it.
};
- try sema.mod.analyzeExport(&block.base, src, options, decl);
+ try sema.mod.analyzeExport(block, src, options, decl);
}
fn zirSetAlignStack(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
From a032fd01e88be2c6e8d0cfb0ccd3d9859c9dffdc Mon Sep 17 00:00:00 2001
From: Stephen Gregoratto
Date: Sat, 18 Sep 2021 23:26:55 +1000
Subject: [PATCH 114/160] Resolve scope IDs using IPv6 sockets
On certain systems (Solaris), resolving the scope id from an interface
name can only be done on AF_INET-domain sockets. While we're here,
simplify the test while we're here, since there's only one address.
Also note that the loopback interface name is not stable across OSs.
BSDs and Solaris use `lo0` whilst Linux uses `l0`.
---
lib/std/x/os/net.zig | 26 +++++++++++---------------
1 file changed, 11 insertions(+), 15 deletions(-)
diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig
index 5b06d492a5..a529396c3c 100644
--- a/lib/std/x/os/net.zig
+++ b/lib/std/x/os/net.zig
@@ -27,7 +27,7 @@ pub fn resolveScopeId(name: []const u8) !u32 {
return rc;
}
- const fd = try os.socket(os.AF.UNIX, os.SOCK.DGRAM, 0);
+ const fd = try os.socket(os.AF.INET, os.SOCK.DGRAM, 0);
defer os.closeSocket(fd);
var f: os.ifreq = undefined;
@@ -566,21 +566,17 @@ test "ipv6: parse & format" {
test "ipv6: parse & format addresses with scope ids" {
if (!have_ifnamesize) return error.SkipZigTest;
+ const iface = if (native_os.tag == .linux)
+ "lo"
+ else
+ "lo0";
+ const input = "FF01::FB%" ++ iface;
+ const output = "ff01::fb%1";
- const inputs = [_][]const u8{
- "FF01::FB%lo",
+ const parsed = IPv6.parse(input) catch |err| switch (err) {
+ error.InterfaceNotFound => return,
+ else => return err,
};
- const outputs = [_][]const u8{
- "ff01::fb%1",
- };
-
- for (inputs) |input, i| {
- const parsed = IPv6.parse(input) catch |err| switch (err) {
- error.InterfaceNotFound => continue,
- else => return err,
- };
-
- try testing.expectFmt(outputs[i], "{}", .{parsed});
- }
+ try testing.expectFmt(output, "{}", .{parsed});
}
From 87fd502fb68f8f488e6eba6b1f7d70902d6bfe5a Mon Sep 17 00:00:00 2001
From: Stephen Gregoratto
Date: Tue, 31 Aug 2021 22:21:23 +1000
Subject: [PATCH 115/160] Initial bringup of the Solaris/Illumos port
---
lib/std/Thread.zig | 15 +-
lib/std/c.zig | 27 +
lib/std/c/solaris.zig | 1920 ++++++++++++++++++++++++++++++-
lib/std/debug.zig | 10 +-
lib/std/dynamic_library.zig | 2 +-
lib/std/fs.zig | 65 +-
lib/std/fs/file.zig | 50 +-
lib/std/fs/get_app_data_dir.zig | 2 +-
lib/std/fs/test.zig | 2 +-
lib/std/os.zig | 19 +
lib/std/os/test.zig | 27 +-
lib/std/process.zig | 3 +-
lib/std/target.zig | 12 +-
lib/std/zig/system.zig | 23 +
src/libc_installation.zig | 1 +
src/link/Elf.zig | 9 +
src/stage1/os.hpp | 2 +
src/stage1/target.cpp | 5 +-
src/target.zig | 7 +
src/type.zig | 2 +-
20 files changed, 2148 insertions(+), 55 deletions(-)
diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig
index 1fe8ca89d2..ae3a3e9d9e 100644
--- a/lib/std/Thread.zig
+++ b/lib/std/Thread.zig
@@ -41,6 +41,7 @@ pub const max_name_len = switch (target.os.tag) {
.netbsd => 31,
.freebsd => 15,
.openbsd => 31,
+ .solaris => 31,
else => 0,
};
@@ -112,7 +113,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void {
else => |e| return os.unexpectedErrno(e),
}
},
- .netbsd => if (use_pthreads) {
+ .netbsd, .solaris => if (use_pthreads) {
const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null);
switch (err) {
.SUCCESS => return,
@@ -202,7 +203,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
else => |e| return os.unexpectedErrno(e),
}
},
- .netbsd => if (use_pthreads) {
+ .netbsd, .solaris => if (use_pthreads) {
const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1);
switch (err) {
.SUCCESS => return std.mem.sliceTo(buffer, 0),
@@ -565,6 +566,16 @@ const PosixThreadImpl = struct {
};
return @intCast(usize, count);
},
+ .solaris => {
+ // The "proper" way to get the cpu count would be to query
+ // /dev/kstat via ioctls, and traverse a linked list for each
+ // cpu.
+ const rc = c.sysconf(os._SC.NPROCESSORS_ONLN);
+ return switch (os.errno(rc)) {
+ .SUCCESS => @intCast(usize, rc),
+ else => |err| os.unexpectedErrno(err),
+ };
+ },
.haiku => {
var count: u32 = undefined;
var system_info: os.system_info = undefined;
diff --git a/lib/std/c.zig b/lib/std/c.zig
index 6a2624a93e..84fbb59640 100644
--- a/lib/std/c.zig
+++ b/lib/std/c.zig
@@ -252,6 +252,33 @@ pub extern "c" fn kevent(
timeout: ?*const c.timespec,
) c_int;
+pub extern "c" fn port_create() c.port_t;
+pub extern "c" fn port_associate(
+ port: c.port_t,
+ source: u32,
+ object: usize,
+ events: u32,
+ user_var: ?*c_void,
+) c_int;
+pub extern "c" fn port_dissociate(port: c.port_t, source: u32, object: usize) c_int;
+pub extern "c" fn port_send(port: c.port_t, events: u32, user_var: ?*c_void) c_int;
+pub extern "c" fn port_sendn(
+ ports: [*]c.port_t,
+ errors: []u32,
+ num_ports: u32,
+ events: u32,
+ user_var: ?*c_void,
+) c_int;
+pub extern "c" fn port_get(port: c.port_t, event: *c.port_event, timeout: ?*c.timespec) c_int;
+pub extern "c" fn port_getn(
+ port: c.port_t,
+ event_list: []c.port_event,
+ max_events: u32,
+ events_retrieved: *u32,
+ timeout: ?*c.timespec,
+) c_int;
+pub extern "c" fn port_alert(port: c.port_t, flags: u32, events: u32, user_var: ?*c_void) c_int;
+
pub extern "c" fn getaddrinfo(
noalias node: ?[*:0]const u8,
noalias service: ?[*:0]const u8,
diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig
index 7c70a01fc4..283ea792cd 100644
--- a/lib/std/c/solaris.zig
+++ b/lib/std/c/solaris.zig
@@ -1,15 +1,1913 @@
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const maxInt = std.math.maxInt;
+const iovec = std.os.iovec;
+const iovec_const = std.os.iovec_const;
+const timezone = std.c.timezone;
+
+extern "c" fn ___errno() *c_int;
+pub const _errno = ___errno;
+
+pub const dl_iterate_phdr_callback = fn (info: *dl_phdr_info, size: usize, data: ?*c_void) callconv(.C) c_int;
+pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int;
+
+pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize;
+pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
+pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int;
+pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;
+pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int;
+pub extern "c" fn sysconf(sc: c_int) i64;
+pub extern "c" fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) c_int;
+pub extern "c" fn madvise(address: [*]u8, len: usize, advise: u32) c_int;
+
pub const pthread_mutex_t = extern struct {
- __pthread_mutex_flag1: u16 = 0,
- __pthread_mutex_flag2: u8 = 0,
- __pthread_mutex_ceiling: u8 = 0,
- __pthread_mutex_type: u16 = 0,
- __pthread_mutex_magic: u16 = 0x4d58,
- __pthread_mutex_lock: u64 = 0,
- __pthread_mutex_data: u64 = 0,
+ flag1: u16 = 0,
+ flag2: u8 = 0,
+ ceiling: u8 = 0,
+ @"type": u16 = 0,
+ magic: u16 = 0x4d58,
+ lock: u64 = 0,
+ data: u64 = 0,
};
pub const pthread_cond_t = extern struct {
- __pthread_cond_flag: u32 = 0,
- __pthread_cond_type: u16 = 0,
- __pthread_cond_magic: u16 = 0x4356,
- __pthread_cond_data: u64 = 0,
+ flag: [4]u8 = [_]u8{0} ** 4,
+ @"type": u16 = 0,
+ magic: u16 = 0x4356,
+ data: u64 = 0,
};
+pub const pthread_rwlock_t = extern struct {
+ readers: i32 = 0,
+ @"type": u16 = 0,
+ magic: u16 = 0x5257,
+ mutex: pthread_mutex_t = .{},
+ readercv: pthread_cond_t = .{},
+ writercv: pthread_cond_t = .{},
+};
+pub const pthread_attr_t = extern struct {
+ mutexattr: ?*c_void = null,
+};
+pub const pthread_key_t = c_int;
+
+pub const sem_t = extern struct {
+ count: u32 = 0,
+ @"type": u16 = 0,
+ magic: u16 = 0x534d,
+ __pad1: [3]u64 = [_]u64{0} ** 3,
+ __pad2: [2]u64 = [_]u64{0} ** 2,
+};
+
+pub extern "c" fn pthread_setname_np(thread: std.c.pthread_t, name: [*:0]const u8, arg: ?*c_void) E;
+pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) E;
+
+pub const blkcnt_t = i64;
+pub const blksize_t = i32;
+pub const clock_t = i64;
+pub const dev_t = i32;
+pub const fd_t = c_int;
+pub const gid_t = u32;
+pub const ino_t = u64;
+pub const mode_t = u32;
+pub const nlink_t = u32;
+pub const off_t = i64;
+pub const pid_t = i32;
+pub const socklen_t = u32;
+pub const time_t = i64;
+pub const suseconds_t = i64;
+pub const uid_t = u32;
+pub const major_t = u32;
+pub const minor_t = u32;
+pub const port_t = c_int;
+pub const nfds_t = usize;
+pub const id_t = i32;
+pub const taskid_t = id_t;
+pub const projid_t = id_t;
+pub const poolid_t = id_t;
+pub const zoneid_t = id_t;
+pub const ctid_t = id_t;
+
+pub const dl_phdr_info = extern struct {
+ dlpi_addr: std.elf.Addr,
+ dlpi_name: ?[*:0]const u8,
+ dlpi_phdr: [*]std.elf.Phdr,
+ dlpi_phnum: std.elf.Half,
+ /// Incremented when a new object is mapped into the process.
+ dlpi_adds: u64,
+ /// Incremented when an object is unmapped from the process.
+ dlpi_subs: u64,
+};
+
+pub const RTLD = struct {
+ pub const LAZY = 0x00001;
+ pub const NOW = 0x00002;
+ pub const NOLOAD = 0x00004;
+ pub const GLOBAL = 0x00100;
+ pub const LOCAL = 0x00000;
+ pub const PARENT = 0x00200;
+ pub const GROUP = 0x00400;
+ pub const WORLD = 0x00800;
+ pub const NODELETE = 0x01000;
+ pub const FIRST = 0x02000;
+ pub const CONFGEN = 0x10000;
+
+ pub const NEXT = @intToPtr(*c_void, @bitCast(usize, @as(isize, -1)));
+ pub const DEFAULT = @intToPtr(*c_void, @bitCast(usize, @as(isize, -2)));
+ pub const SELF = @intToPtr(*c_void, @bitCast(usize, @as(isize, -3)));
+ pub const PROBE = @intToPtr(*c_void, @bitCast(usize, @as(isize, -4)));
+};
+
+pub const Flock = extern struct {
+ l_type: c_short,
+ l_whence: c_short,
+ l_start: off_t,
+ // len == 0 means until end of file.
+ l_len: off_t,
+ l_sysid: c_int,
+ l_pid: pid_t,
+ __pad: [4]c_long,
+};
+
+pub const utsname = extern struct {
+ sysname: [256:0]u8,
+ nodename: [256:0]u8,
+ release: [256:0]u8,
+ version: [256:0]u8,
+ machine: [256:0]u8,
+ domainname: [256:0]u8,
+};
+
+pub const addrinfo = extern struct {
+ flags: i32,
+ family: i32,
+ socktype: i32,
+ protocol: i32,
+ addrlen: socklen_t,
+ canonname: ?[*:0]u8,
+ addr: ?*sockaddr,
+ next: ?*addrinfo,
+};
+
+pub const EAI = enum(c_int) {
+ /// address family for hostname not supported
+ ADDRFAMILY = 1,
+ /// name could not be resolved at this time
+ AGAIN = 2,
+ /// flags parameter had an invalid value
+ BADFLAGS = 3,
+ /// non-recoverable failure in name resolution
+ FAIL = 4,
+ /// address family not recognized
+ FAMILY = 5,
+ /// memory allocation failure
+ MEMORY = 6,
+ /// no address associated with hostname
+ NODATA = 7,
+ /// name does not resolve
+ NONAME = 8,
+ /// service not recognized for socket type
+ SERVICE = 9,
+ /// intended socket type was not recognized
+ SOCKTYPE = 10,
+ /// system error returned in errno
+ SYSTEM = 11,
+ /// argument buffer overflow
+ OVERFLOW = 12,
+ /// resolved protocol is unknown
+ PROTOCOL = 13,
+
+ _,
+};
+
+pub const EAI_MAX = 14;
+
+pub const msghdr = extern struct {
+ /// optional address
+ msg_name: ?*sockaddr,
+ /// size of address
+ msg_namelen: socklen_t,
+ /// scatter/gather array
+ msg_iov: [*]iovec,
+ /// # elements in msg_iov
+ msg_iovlen: i32,
+ /// ancillary data
+ msg_control: ?*c_void,
+ /// ancillary data buffer len
+ msg_controllen: socklen_t,
+ /// flags on received message
+ msg_flags: i32,
+};
+
+pub const msghdr_const = extern struct {
+ /// optional address
+ msg_name: ?*const sockaddr,
+ /// size of address
+ msg_namelen: socklen_t,
+ /// scatter/gather array
+ msg_iov: [*]iovec_const,
+ /// # elements in msg_iov
+ msg_iovlen: i32,
+ /// ancillary data
+ msg_control: ?*c_void,
+ /// ancillary data buffer len
+ msg_controllen: socklen_t,
+ /// flags on received message
+ msg_flags: i32,
+};
+
+pub const cmsghdr = extern struct {
+ cmsg_len: socklen_t,
+ cmsg_level: i32,
+ cmsg_type: i32,
+};
+
+/// The stat structure used by libc.
+pub const Stat = extern struct {
+ dev: dev_t,
+ ino: ino_t,
+ mode: mode_t,
+ nlink: nlink_t,
+ uid: uid_t,
+ gid: gid_t,
+ rdev: dev_t,
+ size: off_t,
+ atim: timespec,
+ mtim: timespec,
+ ctim: timespec,
+ blksize: blksize_t,
+ blocks: blkcnt_t,
+ fstype: [16]u8,
+
+ pub fn atime(self: @This()) timespec {
+ return self.atim;
+ }
+
+ pub fn mtime(self: @This()) timespec {
+ return self.mtim;
+ }
+
+ pub fn ctime(self: @This()) timespec {
+ return self.ctim;
+ }
+};
+
+pub const timespec = extern struct {
+ tv_sec: i64,
+ tv_nsec: isize,
+};
+
+pub const timeval = extern struct {
+ /// seconds
+ tv_sec: time_t,
+ /// microseconds
+ tv_usec: suseconds_t,
+};
+
+pub const MAXNAMLEN = 511;
+
+pub const dirent = extern struct {
+ /// Inode number of entry.
+ d_ino: ino_t,
+ /// Offset of this entry on disk.
+ d_off: off_t,
+ /// Length of this record.
+ d_reclen: u16,
+ /// File name.
+ d_name: [MAXNAMLEN:0]u8,
+
+ pub fn reclen(self: dirent) u16 {
+ return self.d_reclen;
+ }
+};
+
+pub const SOCK = struct {
+ /// Datagram.
+ pub const DGRAM = 1;
+ /// STREAM.
+ pub const STREAM = 2;
+ /// Raw-protocol interface.
+ pub const RAW = 4;
+ /// Reliably-delivered message.
+ pub const RDM = 5;
+ /// Sequenced packed stream.
+ pub const SEQPACKET = 6;
+
+ pub const NONBLOCK = 0x100000;
+ pub const NDELAY = 0x200000;
+ pub const CLOEXEC = 0x080000;
+};
+
+pub const SO = struct {
+ pub const DEBUG = 0x0001;
+ pub const ACCEPTCONN = 0x0002;
+ pub const REUSEADDR = 0x0004;
+ pub const KEEPALIVE = 0x0008;
+ pub const DONTROUTE = 0x0010;
+ pub const BROADCAST = 0x0020;
+ pub const USELOOPBACK = 0x0040;
+ pub const LINGER = 0x0080;
+ pub const OOBINLINE = 0x0100;
+ pub const DGRAM_ERRIND = 0x0200;
+ pub const RECVUCRED = 0x0400;
+
+ pub const SNDBUF = 0x1001;
+ pub const RCVBUF = 0x1002;
+ pub const SNDLOWAT = 0x1003;
+ pub const RCVLOWAT = 0x1004;
+ pub const SNDTIMEO = 0x1005;
+ pub const RCVTIMEO = 0x1006;
+ pub const ERROR = 0x1007;
+ pub const TYPE = 0x1008;
+ pub const PROTOTYPE = 0x1009;
+ pub const ANON_MLP = 0x100a;
+ pub const MAC_EXEMPT = 0x100b;
+ pub const DOMAIN = 0x100c;
+ pub const RCVPSH = 0x100d;
+
+ pub const SECATTR = 0x1011;
+ pub const TIMESTAMP = 0x1013;
+ pub const ALLZONES = 0x1014;
+ pub const EXCLBIND = 0x1015;
+ pub const MAC_IMPLICIT = 0x1016;
+ pub const VRRP = 0x1017;
+};
+
+pub const SOMAXCONN = 128;
+
+pub const SCM = struct {
+ pub const UCRED = 0x1012;
+ pub const RIGHTS = 0x1010;
+ pub const TIMESTAMP = SO.TIMESTAMP;
+};
+
+pub const AF = struct {
+ pub const UNSPEC = 0;
+ pub const UNIX = 1;
+ pub const LOCAL = UNIX;
+ pub const FILE = UNIX;
+ pub const INET = 2;
+ pub const IMPLINK = 3;
+ pub const PUP = 4;
+ pub const CHAOS = 5;
+ pub const NS = 6;
+ pub const NBS = 7;
+ pub const ECMA = 8;
+ pub const DATAKIT = 9;
+ pub const CCITT = 10;
+ pub const SNA = 11;
+ pub const DECnet = 12;
+ pub const DLI = 13;
+ pub const LAT = 14;
+ pub const HYLINK = 15;
+ pub const APPLETALK = 16;
+ pub const NIT = 17;
+ pub const @"802" = 18;
+ pub const OSI = 19;
+ pub const X25 = 20;
+ pub const OSINET = 21;
+ pub const GOSIP = 22;
+ pub const IPX = 23;
+ pub const ROUTE = 24;
+ pub const LINK = 25;
+ pub const INET6 = 26;
+ pub const KEY = 27;
+ pub const NCA = 28;
+ pub const POLICY = 29;
+ pub const INET_OFFLOAD = 30;
+ pub const TRILL = 31;
+ pub const PACKET = 32;
+ pub const LX_NETLINK = 33;
+ pub const MAX = 33;
+};
+
+pub const SOL = struct {
+ pub const SOCKET = 0xffff;
+ pub const ROUTE = 0xfffe;
+ pub const PACKET = 0xfffd;
+ pub const FILTER = 0xfffc;
+};
+
+pub const PF = struct {
+ pub const UNSPEC = AF.UNSPEC;
+ pub const UNIX = AF.UNIX;
+ pub const LOCAL = UNIX;
+ pub const FILE = UNIX;
+ pub const INET = AF.INET;
+ pub const IMPLINK = AF.IMPLINK;
+ pub const PUP = AF.PUP;
+ pub const CHAOS = AF.CHAOS;
+ pub const NS = AF.NS;
+ pub const NBS = AF.NBS;
+ pub const ECMA = AF.ECMA;
+ pub const DATAKIT = AF.DATAKIT;
+ pub const CCITT = AF.CCITT;
+ pub const SNA = AF.SNA;
+ pub const DECnet = AF.DECnet;
+ pub const DLI = AF.DLI;
+ pub const LAT = AF.LAT;
+ pub const HYLINK = AF.HYLINK;
+ pub const APPLETALK = AF.APPLETALK;
+ pub const NIT = AF.NIT;
+ pub const @"802" = AF.@"802";
+ pub const OSI = AF.OSI;
+ pub const X25 = AF.X25;
+ pub const OSINET = AF.OSINET;
+ pub const GOSIP = AF.GOSIP;
+ pub const IPX = AF.IPX;
+ pub const ROUTE = AF.ROUTE;
+ pub const LINK = AF.LINK;
+ pub const INET6 = AF.INET6;
+ pub const KEY = AF.KEY;
+ pub const NCA = AF.NCA;
+ pub const POLICY = AF.POLICY;
+ pub const TRILL = AF.TRILL;
+ pub const PACKET = AF.PACKET;
+ pub const LX_NETLINK = AF.LX_NETLINK;
+ pub const MAX = AF.MAX;
+};
+
+pub const in_port_t = u16;
+pub const sa_family_t = u16;
+
+pub const sockaddr = extern struct {
+ /// address family
+ family: sa_family_t,
+
+ /// actually longer; address value
+ data: [14]u8,
+
+ pub const SS_MAXSIZE = 256;
+ pub const storage = std.x.os.Socket.Address.Native.Storage;
+
+ pub const in = extern struct {
+ family: sa_family_t = AF.INET,
+ port: in_port_t,
+ addr: u32,
+ zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
+ };
+
+ pub const in6 = extern struct {
+ family: sa_family_t = AF.INET6,
+ port: in_port_t,
+ flowinfo: u32,
+ addr: [16]u8,
+ scope_id: u32,
+ __src_id: u32 = 0,
+ };
+
+ /// Definitions for UNIX IPC domain.
+ pub const un = extern struct {
+ family: sa_family_t = AF.UNIX,
+ path: [108]u8,
+ };
+};
+
+pub const AI = struct {
+ /// IPv4-mapped IPv6 address
+ pub const V4MAPPED = 0x0001;
+ pub const ALL = 0x0002;
+ /// only if any address is assigned
+ pub const ADDRCONFIG = 0x0004;
+ /// get address to use bind()
+ pub const PASSIVE = 0x0008;
+ /// fill ai_canonname
+ pub const CANONNAME = 0x0010;
+ /// prevent host name resolution
+ pub const NUMERICHOST = 0x0020;
+ /// prevent service name resolution
+ pub const NUMERICSERV = 0x0040;
+};
+
+pub const NI = struct {
+ pub const NOFQDN = 0x0001;
+ pub const NUMERICHOST = 0x0002;
+ pub const NAMEREQD = 0x0004;
+ pub const NUMERICSERV = 0x0008;
+ pub const DGRAM = 0x0010;
+ pub const WITHSCOPEID = 0x0020;
+ pub const NUMERICSCOPE = 0x0040;
+
+ pub const MAXHOST = 1025;
+ pub const MAXSERV = 32;
+};
+
+pub const PATH_MAX = 1024;
+pub const IOV_MAX = 1024;
+
+pub const STDIN_FILENO = 0;
+pub const STDOUT_FILENO = 1;
+pub const STDERR_FILENO = 2;
+
+pub const PROT = struct {
+ pub const NONE = 0;
+ pub const READ = 1;
+ pub const WRITE = 2;
+ pub const EXEC = 4;
+};
+
+pub const CLOCK = struct {
+ pub const VIRTUAL = 1;
+ pub const THREAD_CPUTIME_ID = 2;
+ pub const REALTIME = 3;
+ pub const MONOTONIC = 4;
+ pub const PROCESS_CPUTIME_ID = 5;
+ pub const HIGHRES = MONOTONIC;
+ pub const PROF = THREAD_CPUTIME_ID;
+};
+
+pub const MAP = struct {
+ pub const FAILED = @intToPtr(*c_void, maxInt(usize));
+ pub const SHARED = 0x0001;
+ pub const PRIVATE = 0x0002;
+ pub const TYPE = 0x000f;
+
+ pub const FILE = 0x0000;
+ pub const FIXED = 0x0010;
+ // Unimplemented
+ pub const RENAME = 0x0020;
+ pub const NORESERVE = 0x0040;
+ /// Force mapping in lower 4G address space
+ pub const @"32BIT" = 0x0080;
+
+ pub const ANON = 0x0100;
+ pub const ANONYMOUS = ANON;
+ pub const ALIGN = 0x0200;
+ pub const TEXT = 0x0400;
+ pub const INITDATA = 0x0800;
+};
+
+pub const MADV = struct {
+ /// no further special treatment
+ pub const NORMAL = 0;
+ /// expect random page references
+ pub const RANDOM = 1;
+ /// expect sequential page references
+ pub const SEQUENTIAL = 2;
+ /// will need these pages
+ pub const WILLNEED = 3;
+ /// don't need these pages
+ pub const DONTNEED = 4;
+ /// contents can be freed
+ pub const FREE = 5;
+ /// default access
+ pub const ACCESS_DEFAULT = 6;
+ /// next LWP to access heavily
+ pub const ACCESS_LWP = 7;
+ /// many processes to access heavily
+ pub const ACCESS_MANY = 8;
+ /// contents will be purged
+ pub const PURGE = 9;
+};
+
+pub const W = struct {
+ pub const EXITED = 0o001;
+ pub const TRAPPED = 0o002;
+ pub const UNTRACED = 0o004;
+ pub const STOPPED = UNTRACED;
+ pub const CONTINUED = 0o010;
+ pub const NOHANG = 0o100;
+ pub const NOWAIT = 0o200;
+
+ pub fn EXITSTATUS(s: u32) u8 {
+ return @intCast(u8, (s >> 8) & 0xff);
+ }
+ pub fn TERMSIG(s: u32) u32 {
+ return s & 0x7f;
+ }
+ pub fn STOPSIG(s: u32) u32 {
+ return EXITSTATUS(s);
+ }
+ pub fn IFEXITED(s: u32) bool {
+ return TERMSIG(s) == 0;
+ }
+
+ pub fn IFCONTINUED(s: u32) bool {
+ return ((s & 0o177777) == 0o177777);
+ }
+
+ pub fn IFSTOPPED(s: u32) bool {
+ return (s & 0x00ff != 0o177) and !(s & 0xff00 != 0);
+ }
+
+ pub fn IFSIGNALED(s: u32) bool {
+ return s & 0x00ff > 0 and s & 0xff00 == 0;
+ }
+};
+
+pub const SA = struct {
+ pub const ONSTACK = 0x00000001;
+ pub const RESETHAND = 0x00000002;
+ pub const RESTART = 0x00000004;
+ pub const SIGINFO = 0x00000008;
+ pub const NODEFER = 0x00000010;
+ pub const NOCLDWAIT = 0x00010000;
+};
+
+// access function
+pub const F_OK = 0; // test for existence of file
+pub const X_OK = 1; // test for execute or search permission
+pub const W_OK = 2; // test for write permission
+pub const R_OK = 4; // test for read permission
+
+pub const F = struct {
+ /// Unlock a previously locked region
+ pub const ULOCK = 0;
+ /// Lock a region for exclusive use
+ pub const LOCK = 1;
+ /// Test and lock a region for exclusive use
+ pub const TLOCK = 2;
+ /// Test a region for other processes locks
+ pub const TEST = 3;
+
+ /// Duplicate fildes
+ pub const DUPFD = 0;
+ /// Get fildes flags
+ pub const GETFD = 1;
+ /// Set fildes flags
+ pub const SETFD = 2;
+ /// Get file flags
+ pub const GETFL = 3;
+ /// Get file flags including open-only flags
+ pub const GETXFL = 45;
+ /// Set file flags
+ pub const SETFL = 4;
+
+ /// Unused
+ pub const CHKFL = 8;
+ /// Duplicate fildes at third arg
+ pub const DUP2FD = 9;
+ /// Like DUP2FD with O_CLOEXEC set EINVAL is fildes matches arg1
+ pub const DUP2FD_CLOEXEC = 36;
+ /// Like DUPFD with O_CLOEXEC set
+ pub const DUPFD_CLOEXEC = 37;
+
+ /// Is the file desc. a stream ?
+ pub const ISSTREAM = 13;
+ /// Turn on private access to file
+ pub const PRIV = 15;
+ /// Turn off private access to file
+ pub const NPRIV = 16;
+ /// UFS quota call
+ pub const QUOTACTL = 17;
+ /// Get number of BLKSIZE blocks allocated
+ pub const BLOCKS = 18;
+ /// Get optimal I/O block size
+ pub const BLKSIZE = 19;
+ /// Get owner (socket emulation)
+ pub const GETOWN = 23;
+ /// Set owner (socket emulation)
+ pub const SETOWN = 24;
+ /// Object reuse revoke access to file desc.
+ pub const REVOKE = 25;
+ /// Does vp have NFS locks private to lock manager
+ pub const HASREMOTELOCKS = 26;
+
+ /// Set file lock
+ pub const SETLK = 6;
+ /// Set file lock and wait
+ pub const SETLKW = 7;
+ /// Allocate file space
+ pub const ALLOCSP = 10;
+ /// Free file space
+ pub const FREESP = 11;
+ /// Get file lock
+ pub const GETLK = 14;
+ /// Get file lock owned by file
+ pub const OFD_GETLK = 47;
+ /// Set file lock owned by file
+ pub const OFD_SETLK = 48;
+ /// Set file lock owned by file and wait
+ pub const OFD_SETLKW = 49;
+ /// Set a file share reservation
+ pub const SHARE = 40;
+ /// Remove a file share reservation
+ pub const UNSHARE = 41;
+ /// Create Poison FD
+ pub const BADFD = 46;
+
+ /// Read lock
+ pub const RDLCK = 1;
+ /// Write lock
+ pub const WRLCK = 2;
+ /// Remove lock(s)
+ pub const UNLCK = 3;
+ /// remove remote locks for a given system
+ pub const UNLKSYS = 4;
+
+ // f_access values
+ /// Read-only share access
+ pub const RDACC = 0x1;
+ /// Write-only share access
+ pub const WRACC = 0x2;
+ /// Read-Write share access
+ pub const RWACC = 0x3;
+
+ // f_deny values
+ /// Don't deny others access
+ pub const NODNY = 0x0;
+ /// Deny others read share access
+ pub const RDDNY = 0x1;
+ /// Deny others write share access
+ pub const WRDNY = 0x2;
+ /// Deny others read or write share access
+ pub const RWDNY = 0x3;
+ /// private flag: Deny delete share access
+ pub const RMDNY = 0x4;
+};
+
+pub const O = struct {
+ pub const RDONLY = 0;
+ pub const WRONLY = 1;
+ pub const RDWR = 2;
+ pub const SEARCH = 0x200000;
+ pub const EXEC = 0x400000;
+ pub const NDELAY = 0x04;
+ pub const APPEND = 0x08;
+ pub const SYNC = 0x10;
+ pub const DSYNC = 0x40;
+ pub const RSYNC = 0x8000;
+ pub const NONBLOCK = 0x80;
+ pub const LARGEFILE = 0x2000;
+
+ pub const CREAT = 0x100;
+ pub const TRUNC = 0x200;
+ pub const EXCL = 0x400;
+ pub const NOCTTY = 0x800;
+ pub const XATTR = 0x4000;
+ pub const NOFOLLOW = 0x20000;
+ pub const NOLINKS = 0x40000;
+ pub const CLOEXEC = 0x800000;
+ pub const DIRECTORY = 0x1000000;
+ pub const DIRECT = 0x2000000;
+};
+
+pub const LOCK = struct {
+ pub const SH = 1;
+ pub const EX = 2;
+ pub const NB = 4;
+ pub const UN = 8;
+};
+
+pub const FD_CLOEXEC = 1;
+
+pub const SEEK = struct {
+ pub const SET = 0;
+ pub const CUR = 1;
+ pub const END = 2;
+ pub const DATA = 3;
+ pub const HOLE = 4;
+};
+
+pub const tcflag_t = c_uint;
+pub const cc_t = u8;
+pub const speed_t = c_uint;
+
+pub const NCCS = 19;
+
+pub const termios = extern struct {
+ c_iflag: tcflag_t,
+ c_oflag: tcflag_t,
+ c_cflag: tcflag_t,
+ c_lflag: tcflag_t,
+ c_cc: [NCCS]cc_t,
+};
+
+fn tioc(t: u16, num: u8) u16 {
+ return (t << 8) | num;
+}
+
+pub const T = struct {
+ pub const CGETA = tioc('T', 1);
+ pub const CSETA = tioc('T', 2);
+ pub const CSETAW = tioc('T', 3);
+ pub const CSETAF = tioc('T', 4);
+ pub const CSBRK = tioc('T', 5);
+ pub const CXONC = tioc('T', 6);
+ pub const CFLSH = tioc('T', 7);
+ pub const IOCGWINSZ = tioc('T', 104);
+ pub const IOCSWINSZ = tioc('T', 103);
+ // Softcarrier ioctls
+ pub const IOCGSOFTCAR = tioc('T', 105);
+ pub const IOCSSOFTCAR = tioc('T', 106);
+ // termios ioctls
+ pub const CGETS = tioc('T', 13);
+ pub const CSETS = tioc('T', 14);
+ pub const CSANOW = tioc('T', 14);
+ pub const CSETSW = tioc('T', 15);
+ pub const CSADRAIN = tioc('T', 15);
+ pub const CSETSF = tioc('T', 16);
+ pub const IOCSETLD = tioc('T', 123);
+ pub const IOCGETLD = tioc('T', 124);
+ // NTP PPS ioctls
+ pub const IOCGPPS = tioc('T', 125);
+ pub const IOCSPPS = tioc('T', 126);
+ pub const IOCGPPSEV = tioc('T', 127);
+
+ pub const IOCGETD = tioc('t', 0);
+ pub const IOCSETD = tioc('t', 1);
+ pub const IOCHPCL = tioc('t', 2);
+ pub const IOCGETP = tioc('t', 8);
+ pub const IOCSETP = tioc('t', 9);
+ pub const IOCSETN = tioc('t', 10);
+ pub const IOCEXCL = tioc('t', 13);
+ pub const IOCNXCL = tioc('t', 14);
+ pub const IOCFLUSH = tioc('t', 16);
+ pub const IOCSETC = tioc('t', 17);
+ pub const IOCGETC = tioc('t', 18);
+ /// bis local mode bits
+ pub const IOCLBIS = tioc('t', 127);
+ /// bic local mode bits
+ pub const IOCLBIC = tioc('t', 126);
+ /// set entire local mode word
+ pub const IOCLSET = tioc('t', 125);
+ /// get local modes
+ pub const IOCLGET = tioc('t', 124);
+ /// set break bit
+ pub const IOCSBRK = tioc('t', 123);
+ /// clear break bit
+ pub const IOCCBRK = tioc('t', 122);
+ /// set data terminal ready
+ pub const IOCSDTR = tioc('t', 121);
+ /// clear data terminal ready
+ pub const IOCCDTR = tioc('t', 120);
+ /// set local special chars
+ pub const IOCSLTC = tioc('t', 117);
+ /// get local special chars
+ pub const IOCGLTC = tioc('t', 116);
+ /// driver output queue size
+ pub const IOCOUTQ = tioc('t', 115);
+ /// void tty association
+ pub const IOCNOTTY = tioc('t', 113);
+ /// get a ctty
+ pub const IOCSCTTY = tioc('t', 132);
+ /// stop output, like ^S
+ pub const IOCSTOP = tioc('t', 111);
+ /// start output, like ^Q
+ pub const IOCSTART = tioc('t', 110);
+ /// get pgrp of tty
+ pub const IOCGPGRP = tioc('t', 20);
+ /// set pgrp of tty
+ pub const IOCSPGRP = tioc('t', 21);
+ /// get session id on ctty
+ pub const IOCGSID = tioc('t', 22);
+ /// simulate terminal input
+ pub const IOCSTI = tioc('t', 23);
+ /// set all modem bits
+ pub const IOCMSET = tioc('t', 26);
+ /// bis modem bits
+ pub const IOCMBIS = tioc('t', 27);
+ /// bic modem bits
+ pub const IOCMBIC = tioc('t', 28);
+ /// get all modem bits
+ pub const IOCMGET = tioc('t', 29);
+};
+
+pub const winsize = extern struct {
+ ws_row: u16,
+ ws_col: u16,
+ ws_xpixel: u16,
+ ws_ypixel: u16,
+};
+
+const NSIG = 75;
+
+pub const SIG = struct {
+ pub const DFL = @intToPtr(?Sigaction.sigaction_fn, 0);
+ pub const ERR = @intToPtr(?Sigaction.sigaction_fn, maxInt(usize));
+ pub const IGN = @intToPtr(?Sigaction.sigaction_fn, 1);
+ pub const HOLD = @intToPtr(?Sigaction.sigaction_fn, 2);
+
+ pub const WORDS = 4;
+ pub const MAXSIG = 75;
+
+ pub const SIG_BLOCK = 1;
+ pub const SIG_UNBLOCK = 2;
+ pub const SIG_SETMASK = 3;
+
+ pub const HUP = 1;
+ pub const INT = 2;
+ pub const QUIT = 3;
+ pub const ILL = 4;
+ pub const TRAP = 5;
+ pub const IOT = 6;
+ pub const ABRT = 6;
+ pub const EMT = 7;
+ pub const FPE = 8;
+ pub const KILL = 9;
+ pub const BUS = 10;
+ pub const SEGV = 11;
+ pub const SYS = 12;
+ pub const PIPE = 13;
+ pub const ALRM = 14;
+ pub const TERM = 15;
+ pub const USR1 = 16;
+ pub const USR2 = 17;
+ pub const CLD = 18;
+ pub const CHLD = 18;
+ pub const PWR = 19;
+ pub const WINCH = 20;
+ pub const URG = 21;
+ pub const POLL = 22;
+ pub const IO = .POLL;
+ pub const STOP = 23;
+ pub const TSTP = 24;
+ pub const CONT = 25;
+ pub const TTIN = 26;
+ pub const TTOU = 27;
+ pub const VTALRM = 28;
+ pub const PROF = 29;
+ pub const XCPU = 30;
+ pub const XFSZ = 31;
+ pub const WAITING = 32;
+ pub const LWP = 33;
+ pub const FREEZE = 34;
+ pub const THAW = 35;
+ pub const CANCEL = 36;
+ pub const LOST = 37;
+ pub const XRES = 38;
+ pub const JVM1 = 39;
+ pub const JVM2 = 40;
+ pub const INFO = 41;
+
+ pub const RTMIN = 42;
+ pub const RTMAX = 74;
+
+ pub inline fn IDX(sig: usize) usize {
+ return sig - 1;
+ }
+ pub inline fn WORD(sig: usize) usize {
+ return IDX(sig) >> 5;
+ }
+ pub inline fn BIT(sig: usize) usize {
+ return 1 << (IDX(sig) & 31);
+ }
+ pub inline fn VALID(sig: usize) usize {
+ return sig <= MAXSIG and sig > 0;
+ }
+};
+
+/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.
+pub const Sigaction = extern struct {
+ pub const handler_fn = fn (c_int) callconv(.C) void;
+ pub const sigaction_fn = fn (c_int, *const siginfo_t, ?*const c_void) callconv(.C) void;
+
+ /// signal options
+ flags: c_uint,
+ /// signal handler
+ handler: extern union {
+ handler: ?handler_fn,
+ sigaction: ?sigaction_fn,
+ },
+ /// signal mask to apply
+ mask: sigset_t,
+};
+
+pub const sigval_t = extern union {
+ int: c_int,
+ ptr: ?*c_void,
+};
+
+pub const siginfo_t = extern struct {
+ signo: c_int,
+ code: c_int,
+ errno: c_int,
+ // 64bit architectures insert 4bytes of padding here, this is done by
+ // correctly aligning the reason field
+ reason: extern union {
+ proc: extern struct {
+ pid: pid_t,
+ pdata: extern union {
+ kill: extern struct {
+ uid: uid_t,
+ value: sigval_t,
+ },
+ cld: extern struct {
+ utime: clock_t,
+ status: c_int,
+ stime: clock_t,
+ },
+ },
+ contract: ctid_t,
+ zone: zoneid_t,
+ },
+ fault: extern struct {
+ addr: ?*c_void,
+ trapno: c_int,
+ pc: ?*c_void,
+ },
+ file: extern struct {
+ // fd not currently available for SIGPOLL.
+ fd: c_int,
+ band: c_long,
+ },
+ prof: extern struct {
+ addr: ?*c_void,
+ timestamp: timespec,
+ syscall: c_short,
+ sysarg: u8,
+ fault: u8,
+ args: [8]c_long,
+ state: [10]c_int,
+ },
+ rctl: extern struct {
+ entity: i32,
+ },
+ __pad: [256 - 4 * @sizeOf(c_int)]u8,
+ } align(@sizeOf(usize)),
+};
+
+comptime {
+ std.debug.assert(@sizeOf(siginfo_t) == 256);
+ std.debug.assert(@alignOf(siginfo_t) == @sizeOf(usize));
+}
+
+pub const sigset_t = extern struct {
+ __bits: [SIG.WORDS]u32,
+};
+
+pub const empty_sigset = sigset_t{ .__bits = [_]u32{0} ** SIG.WORDS };
+
+pub const fpregset_t = extern union {
+ regs: [130]u32,
+ chip_state: extern struct {
+ cw: u16,
+ sw: u16,
+ fctw: u8,
+ __fx_rsvd: u8,
+ fop: u16,
+ rip: u64,
+ rdp: u64,
+ mxcsr: u32,
+ mxcsr_mask: u32,
+ st: [8]extern union {
+ fpr_16: [5]u16,
+ __fpr_pad: u128,
+ },
+ xmm: [16]u128,
+ __fx_ign2: [6]u128,
+ status: u32,
+ xstatus: u32,
+ },
+};
+
+pub const mcontext_t = extern struct {
+ gregs: [28]u64,
+ fpregs: fpregset_t,
+};
+
+pub const REG = struct {
+ pub const RBP = 10;
+ pub const RIP = 17;
+ pub const RSP = 20;
+};
+
+pub const ucontext_t = extern struct {
+ flags: u64,
+ link: ?*ucontext_t,
+ sigmask: sigset_t,
+ stack: stack_t,
+ mcontext: mcontext_t,
+ brand_data: [3]?*c_void,
+ filler: [2]i64,
+};
+
+pub const GETCONTEXT = 0;
+pub const SETCONTEXT = 1;
+pub const GETUSTACK = 2;
+pub const SETUSTACK = 3;
+
+pub const E = enum(u16) {
+ /// No error occurred.
+ SUCCESS = 0,
+ /// Not super-user
+ PERM = 1,
+ /// No such file or directory
+ NOENT = 2,
+ /// No such process
+ SRCH = 3,
+ /// interrupted system call
+ INTR = 4,
+ /// I/O error
+ IO = 5,
+ /// No such device or address
+ NXIO = 6,
+ /// Arg list too long
+ @"2BIG" = 7,
+ /// Exec format error
+ NOEXEC = 8,
+ /// Bad file number
+ BADF = 9,
+ /// No children
+ CHILD = 10,
+ /// Resource temporarily unavailable.
+ /// also: WOULDBLOCK: Operation would block.
+ AGAIN = 11,
+ /// Not enough core
+ NOMEM = 12,
+ /// Permission denied
+ ACCES = 13,
+ /// Bad address
+ FAULT = 14,
+ /// Block device required
+ NOTBLK = 15,
+ /// Mount device busy
+ BUSY = 16,
+ /// File exists
+ EXIST = 17,
+ /// Cross-device link
+ XDEV = 18,
+ /// No such device
+ NODEV = 19,
+ /// Not a directory
+ NOTDIR = 20,
+ /// Is a directory
+ ISDIR = 21,
+ /// Invalid argument
+ INVAL = 22,
+ /// File table overflow
+ NFILE = 23,
+ /// Too many open files
+ MFILE = 24,
+ /// Inappropriate ioctl for device
+ NOTTY = 25,
+ /// Text file busy
+ TXTBSY = 26,
+ /// File too large
+ FBIG = 27,
+ /// No space left on device
+ NOSPC = 28,
+ /// Illegal seek
+ SPIPE = 29,
+ /// Read only file system
+ ROFS = 30,
+ /// Too many links
+ MLINK = 31,
+ /// Broken pipe
+ PIPE = 32,
+ /// Math arg out of domain of func
+ DOM = 33,
+ /// Math result not representable
+ RANGE = 34,
+ /// No message of desired type
+ NOMSG = 35,
+ /// Identifier removed
+ IDRM = 36,
+ /// Channel number out of range
+ CHRNG = 37,
+ /// Level 2 not synchronized
+ L2NSYNC = 38,
+ /// Level 3 halted
+ L3HLT = 39,
+ /// Level 3 reset
+ L3RST = 40,
+ /// Link number out of range
+ LNRNG = 41,
+ /// Protocol driver not attached
+ UNATCH = 42,
+ /// No CSI structure available
+ NOCSI = 43,
+ /// Level 2 halted
+ L2HLT = 44,
+ /// Deadlock condition.
+ DEADLK = 45,
+ /// No record locks available.
+ NOLCK = 46,
+ /// Operation canceled
+ CANCELED = 47,
+ /// Operation not supported
+ NOTSUP = 48,
+
+ // Filesystem Quotas
+ /// Disc quota exceeded
+ DQUOT = 49,
+
+ // Convergent Error Returns
+ /// invalid exchange
+ BADE = 50,
+ /// invalid request descriptor
+ BADR = 51,
+ /// exchange full
+ XFULL = 52,
+ /// no anode
+ NOANO = 53,
+ /// invalid request code
+ BADRQC = 54,
+ /// invalid slot
+ BADSLT = 55,
+ /// file locking deadlock error
+ DEADLOCK = 56,
+ /// bad font file fmt
+ BFONT = 57,
+
+ // Interprocess Robust Locks
+ /// process died with the lock
+ OWNERDEAD = 58,
+ /// lock is not recoverable
+ NOTRECOVERABLE = 59,
+ /// locked lock was unmapped
+ LOCKUNMAPPED = 72,
+ /// Facility is not active
+ NOTACTIVE = 73,
+ /// multihop attempted
+ MULTIHOP = 74,
+ /// trying to read unreadable message
+ BADMSG = 77,
+ /// path name is too long
+ NAMETOOLONG = 78,
+ /// value too large to be stored in data type
+ OVERFLOW = 79,
+ /// given log. name not unique
+ NOTUNIQ = 80,
+ /// f.d. invalid for this operation
+ BADFD = 81,
+ /// Remote address changed
+ REMCHG = 82,
+
+ // Stream Problems
+ /// Device not a stream
+ NOSTR = 60,
+ /// no data (for no delay io)
+ NODATA = 61,
+ /// timer expired
+ TIME = 62,
+ /// out of streams resources
+ NOSR = 63,
+ /// Machine is not on the network
+ NONET = 64,
+ /// Package not installed
+ NOPKG = 65,
+ /// The object is remote
+ REMOTE = 66,
+ /// the link has been severed
+ NOLINK = 67,
+ /// advertise error
+ ADV = 68,
+ /// srmount error
+ SRMNT = 69,
+ /// Communication error on send
+ COMM = 70,
+ /// Protocol error
+ PROTO = 71,
+
+ // Shared Library Problems
+ /// Can't access a needed shared lib.
+ LIBACC = 83,
+ /// Accessing a corrupted shared lib.
+ LIBBAD = 84,
+ /// .lib section in a.out corrupted.
+ LIBSCN = 85,
+ /// Attempting to link in too many libs.
+ LIBMAX = 86,
+ /// Attempting to exec a shared library.
+ LIBEXEC = 87,
+ /// Illegal byte sequence.
+ ILSEQ = 88,
+ /// Unsupported file system operation
+ NOSYS = 89,
+ /// Symbolic link loop
+ LOOP = 90,
+ /// Restartable system call
+ RESTART = 91,
+ /// if pipe/FIFO, don't sleep in stream head
+ STRPIPE = 92,
+ /// directory not empty
+ NOTEMPTY = 93,
+ /// Too many users (for UFS)
+ USERS = 94,
+
+ // BSD Networking Software
+ // Argument Errors
+ /// Socket operation on non-socket
+ NOTSOCK = 95,
+ /// Destination address required
+ DESTADDRREQ = 96,
+ /// Message too long
+ MSGSIZE = 97,
+ /// Protocol wrong type for socket
+ PROTOTYPE = 98,
+ /// Protocol not available
+ NOPROTOOPT = 99,
+ /// Protocol not supported
+ PROTONOSUPPORT = 120,
+ /// Socket type not supported
+ SOCKTNOSUPPORT = 121,
+ /// Operation not supported on socket
+ OPNOTSUPP = 122,
+ /// Protocol family not supported
+ PFNOSUPPORT = 123,
+ /// Address family not supported by
+ AFNOSUPPORT = 124,
+ /// Address already in use
+ ADDRINUSE = 125,
+ /// Can't assign requested address
+ ADDRNOTAVAIL = 126,
+
+ // Operational Errors
+ /// Network is down
+ NETDOWN = 127,
+ /// Network is unreachable
+ NETUNREACH = 128,
+ /// Network dropped connection because
+ NETRESET = 129,
+ /// Software caused connection abort
+ CONNABORTED = 130,
+ /// Connection reset by peer
+ CONNRESET = 131,
+ /// No buffer space available
+ NOBUFS = 132,
+ /// Socket is already connected
+ ISCONN = 133,
+ /// Socket is not connected
+ NOTCONN = 134,
+ /// Can't send after socket shutdown
+ SHUTDOWN = 143,
+ /// Too many references: can't splice
+ TOOMANYREFS = 144,
+ /// Connection timed out
+ TIMEDOUT = 145,
+ /// Connection refused
+ CONNREFUSED = 146,
+ /// Host is down
+ HOSTDOWN = 147,
+ /// No route to host
+ HOSTUNREACH = 148,
+ /// operation already in progress
+ ALREADY = 149,
+ /// operation now in progress
+ INPROGRESS = 150,
+
+ // SUN Network File System
+ /// Stale NFS file handle
+ STALE = 151,
+
+ _,
+};
+
+pub const MINSIGSTKSZ = 2048;
+pub const SIGSTKSZ = 8192;
+
+pub const SS_ONSTACK = 0x1;
+pub const SS_DISABLE = 0x2;
+
+pub const stack_t = extern struct {
+ sp: [*]u8,
+ size: isize,
+ flags: i32,
+};
+
+pub const S = struct {
+ pub const IFMT = 0o170000;
+
+ pub const IFIFO = 0o010000;
+ pub const IFCHR = 0o020000;
+ pub const IFDIR = 0o040000;
+ pub const IFBLK = 0o060000;
+ pub const IFREG = 0o100000;
+ pub const IFLNK = 0o120000;
+ pub const IFSOCK = 0o140000;
+ /// SunOS 2.6 Door
+ pub const IFDOOR = 0o150000;
+ /// Solaris 10 Event Port
+ pub const IFPORT = 0o160000;
+
+ pub const ISUID = 0o4000;
+ pub const ISGID = 0o2000;
+ pub const ISVTX = 0o1000;
+ pub const IRWXU = 0o700;
+ pub const IRUSR = 0o400;
+ pub const IWUSR = 0o200;
+ pub const IXUSR = 0o100;
+ pub const IRWXG = 0o070;
+ pub const IRGRP = 0o040;
+ pub const IWGRP = 0o020;
+ pub const IXGRP = 0o010;
+ pub const IRWXO = 0o007;
+ pub const IROTH = 0o004;
+ pub const IWOTH = 0o002;
+ pub const IXOTH = 0o001;
+
+ pub fn ISFIFO(m: u32) bool {
+ return m & IFMT == IFIFO;
+ }
+
+ pub fn ISCHR(m: u32) bool {
+ return m & IFMT == IFCHR;
+ }
+
+ pub fn ISDIR(m: u32) bool {
+ return m & IFMT == IFDIR;
+ }
+
+ pub fn ISBLK(m: u32) bool {
+ return m & IFMT == IFBLK;
+ }
+
+ pub fn ISREG(m: u32) bool {
+ return m & IFMT == IFREG;
+ }
+
+ pub fn ISLNK(m: u32) bool {
+ return m & IFMT == IFLNK;
+ }
+
+ pub fn ISSOCK(m: u32) bool {
+ return m & IFMT == IFSOCK;
+ }
+
+ pub fn ISDOOR(m: u32) bool {
+ return m & IFMT == IFDOOR;
+ }
+
+ pub fn ISPORT(m: u32) bool {
+ return m & IFMT == IFPORT;
+ }
+};
+
+pub const AT = struct {
+ /// Magic value that specify the use of the current working directory
+ /// to determine the target of relative file paths in the openat() and
+ /// similar syscalls.
+ pub const FDCWD = @bitCast(fd_t, @as(u32, 0xffd19553));
+
+ /// Do not follow symbolic links
+ pub const SYMLINK_NOFOLLOW = 0x1000;
+ /// Follow symbolic link
+ pub const SYMLINK_FOLLOW = 0x2000;
+ /// Remove directory instead of file
+ pub const REMOVEDIR = 0x1;
+ pub const TRIGGER = 0x2;
+ /// Check access using effective user and group ID
+ pub const EACCESS = 0x4;
+};
+
+pub const POSIX_FADV = struct {
+ pub const NORMAL = 0;
+ pub const RANDOM = 1;
+ pub const SEQUENTIAL = 2;
+ pub const WILLNEED = 3;
+ pub const DONTNEED = 4;
+ pub const NOREUSE = 5;
+};
+
+pub const HOST_NAME_MAX = 255;
+
+pub const IPPROTO = struct {
+ /// dummy for IP
+ pub const IP = 0;
+ /// Hop by hop header for IPv6
+ pub const HOPOPTS = 0;
+ /// control message protocol
+ pub const ICMP = 1;
+ /// group control protocol
+ pub const IGMP = 2;
+ /// gateway^2 (deprecated)
+ pub const GGP = 3;
+ /// IP in IP encapsulation
+ pub const ENCAP = 4;
+ /// tcp
+ pub const TCP = 6;
+ /// exterior gateway protocol
+ pub const EGP = 8;
+ /// pup
+ pub const PUP = 12;
+ /// user datagram protocol
+ pub const UDP = 17;
+ /// xns idp
+ pub const IDP = 22;
+ /// IPv6 encapsulated in IP
+ pub const IPV6 = 41;
+ /// Routing header for IPv6
+ pub const ROUTING = 43;
+ /// Fragment header for IPv6
+ pub const FRAGMENT = 44;
+ /// rsvp
+ pub const RSVP = 46;
+ /// IPsec Encap. Sec. Payload
+ pub const ESP = 50;
+ /// IPsec Authentication Hdr.
+ pub const AH = 51;
+ /// ICMP for IPv6
+ pub const ICMPV6 = 58;
+ /// No next header for IPv6
+ pub const NONE = 59;
+ /// Destination options
+ pub const DSTOPTS = 60;
+ /// "hello" routing protocol
+ pub const HELLO = 63;
+ /// UNOFFICIAL net disk proto
+ pub const ND = 77;
+ /// ISO clnp
+ pub const EON = 80;
+ /// OSPF
+ pub const OSPF = 89;
+ /// PIM routing protocol
+ pub const PIM = 103;
+ /// Stream Control
+ pub const SCTP = 132;
+ /// raw IP packet
+ pub const RAW = 255;
+ /// Sockets Direct Protocol
+ pub const PROTO_SDP = 257;
+};
+
+pub const priority = enum(c_int) {
+ PROCESS = 0,
+ PGRP = 1,
+ USER = 2,
+ GROUP = 3,
+ SESSION = 4,
+ LWP = 5,
+ TASK = 6,
+ PROJECT = 7,
+ ZONE = 8,
+ CONTRACT = 9,
+};
+
+pub const rlimit_resource = enum(c_int) {
+ CPU = 0,
+ FSIZE = 1,
+ DATA = 2,
+ STACK = 3,
+ CORE = 4,
+ NOFILE = 5,
+ VMEM = 6,
+ _,
+
+ pub const AS: rlimit_resource = .VMEM;
+};
+
+pub const rlim_t = u64;
+
+pub const RLIM = struct {
+ /// No limit
+ pub const INFINITY: rlim_t = (1 << 63) - 3;
+ pub const SAVED_MAX: rlim_t = (1 << 63) - 2;
+ pub const SAVED_CUR: rlim_t = (1 << 63) - 1;
+};
+
+pub const rlimit = extern struct {
+ /// Soft limit
+ cur: rlim_t,
+ /// Hard limit
+ max: rlim_t,
+};
+
+pub const RUSAGE_SELF = 0;
+pub const RUSAGE_CHILDREN = -1;
+pub const RUSAGE_THREAD = 1;
+
+pub const rusage = extern struct {
+ utime: timeval,
+ stime: timeval,
+ maxrss: isize,
+ ixrss: isize,
+ idrss: isize,
+ isrss: isize,
+ minflt: isize,
+ majflt: isize,
+ nswap: isize,
+ inblock: isize,
+ oublock: isize,
+ msgsnd: isize,
+ msgrcv: isize,
+ nsignals: isize,
+ nvcsw: isize,
+ nivcsw: isize,
+};
+
+pub const SHUT = struct {
+ pub const RD = 0;
+ pub const WR = 1;
+ pub const RDWR = 2;
+};
+
+pub const pollfd = extern struct {
+ fd: fd_t,
+ events: i16,
+ revents: i16,
+};
+
+/// Testable events (may be specified in ::pollfd::events).
+pub const POLL = struct {
+ pub const IN = 0x0001;
+ pub const PRI = 0x0002;
+ pub const OUT = 0x0004;
+ pub const RDNORM = 0x0040;
+ pub const WRNORM = .OUT;
+ pub const RDBAND = 0x0080;
+ pub const WRBAND = 0x0100;
+ /// Read-side hangup.
+ pub const RDHUP = 0x4000;
+
+ /// Non-testable events (may not be specified in events).
+ pub const ERR = 0x0008;
+ pub const HUP = 0x0010;
+ pub const NVAL = 0x0020;
+
+ /// Events to control `/dev/poll` (not specified in revents)
+ pub const REMOVE = 0x0800;
+ pub const ONESHOT = 0x1000;
+ pub const ET = 0x2000;
+};
+
+/// Extensions to the ELF auxiliary vector.
+pub const AT_SUN = struct {
+ /// effective user id
+ pub const UID = 2000;
+ /// real user id
+ pub const RUID = 2001;
+ /// effective group id
+ pub const GID = 2002;
+ /// real group id
+ pub const RGID = 2003;
+ /// dynamic linker's ELF header
+ pub const LDELF = 2004;
+ /// dynamic linker's section headers
+ pub const LDSHDR = 2005;
+ /// name of dynamic linker
+ pub const LDNAME = 2006;
+ /// large pagesize
+ pub const LPAGESZ = 2007;
+ /// platform name
+ pub const PLATFORM = 2008;
+ /// hints about hardware capabilities.
+ pub const HWCAP = 2009;
+ pub const HWCAP2 = 2023;
+ /// flush icache?
+ pub const IFLUSH = 2010;
+ /// cpu name
+ pub const CPU = 2011;
+ /// exec() path name in the auxv, null terminated.
+ pub const EXECNAME = 2014;
+ /// mmu module name
+ pub const MMU = 2015;
+ /// dynamic linkers data segment
+ pub const LDDATA = 2016;
+ /// AF_SUN_ flags passed from the kernel
+ pub const AUXFLAGS = 2017;
+ /// name of the emulation binary for the linker
+ pub const EMULATOR = 2018;
+ /// name of the brand library for the linker
+ pub const BRANDNAME = 2019;
+ /// vectors for brand modules.
+ pub const BRAND_AUX1 = 2020;
+ pub const BRAND_AUX2 = 2021;
+ pub const BRAND_AUX3 = 2022;
+ pub const BRAND_AUX4 = 2025;
+ pub const BRAND_NROOT = 2024;
+ /// vector for comm page.
+ pub const COMMPAGE = 2026;
+ /// information about the x86 FPU.
+ pub const FPTYPE = 2027;
+ pub const FPSIZE = 2028;
+};
+
+/// ELF auxiliary vector flags.
+pub const AF_SUN = struct {
+ /// tell ld.so.1 to run "secure" and ignore the environment.
+ pub const SETUGID = 0x00000001;
+ /// hardware capabilities can be verified against AT_SUN_HWCAP
+ pub const HWCAPVERIFY = 0x00000002;
+ pub const NOPLM = 0x00000004;
+};
+
+// TODO: Add sysconf numbers when the other OSs do.
+pub const _SC = struct {
+ pub const NPROCESSORS_ONLN = 15;
+};
+
+pub const procfs = struct {
+ pub const misc_header = extern struct {
+ size: u32,
+ @"type": enum(u32) {
+ Pathname,
+ Socketname,
+ Peersockname,
+ SockoptsBoolOpts,
+ SockoptLinger,
+ SockoptSndbuf,
+ SockoptRcvbuf,
+ SockoptIpNexthop,
+ SockoptIpv6Nexthop,
+ SockoptType,
+ SockoptTcpCongestion,
+ SockfiltersPriv = 14,
+ },
+ };
+
+ pub const fdinfo = extern struct {
+ fd: fd_t,
+ mode: mode_t,
+ ino: ino_t,
+ size: off_t,
+ offset: off_t,
+ uid: uid_t,
+ gid: gid_t,
+ dev_major: major_t,
+ dev_minor: minor_t,
+ special_major: major_t,
+ special_minor: minor_t,
+ fileflags: i32,
+ fdflags: i32,
+ locktype: i16,
+ lockpid: pid_t,
+ locksysid: i32,
+ peerpid: pid_t,
+ __filler: [25]c_int,
+ peername: [15:0]u8,
+ misc: [1]u8,
+ };
+};
+
+pub const SFD = struct {
+ pub const CLOEXEC = 0o2000000;
+ pub const NONBLOCK = 0o4000;
+};
+
+pub const signalfd_siginfo = extern struct {
+ signo: u32,
+ errno: i32,
+ code: i32,
+ pid: u32,
+ uid: uid_t,
+ fd: i32,
+ tid: u32, // unused
+ band: u32,
+ overrun: u32, // unused
+ trapno: u32,
+ status: i32,
+ int: i32, // unused
+ ptr: u64, // unused
+ utime: u64,
+ stime: u64,
+ addr: u64,
+ __pad: [48]u8,
+};
+
+pub const PORT_SOURCE = struct {
+ pub const AIO = 1;
+ pub const TIMER = 2;
+ pub const USER = 3;
+ pub const FD = 4;
+ pub const ALERT = 5;
+ pub const MQ = 6;
+ pub const FILE = 7;
+};
+
+pub const PORT_ALERT = struct {
+ pub const SET = 0x01;
+ pub const UPDATE = 0x02;
+};
+
+/// User watchable file events.
+pub const FILE_EVENT = struct {
+ pub const ACCESS = 0x00000001;
+ pub const MODIFIED = 0x00000002;
+ pub const ATTRIB = 0x00000004;
+ pub const DELETE = 0x00000010;
+ pub const RENAME_TO = 0x00000020;
+ pub const RENAME_FROM = 0x00000040;
+ pub const TRUNC = 0x00100000;
+ pub const NOFOLLOW = 0x10000000;
+ /// The filesystem holding the watched file was unmounted.
+ pub const UNMOUNTED = 0x20000000;
+ /// Some other file/filesystem got mounted over the watched file/directory.
+ pub const MOUNTEDOVER = 0x40000000;
+
+ pub fn isException(event: u32) bool {
+ return event & (UNMOUNTED | DELETE | RENAME_TO | RENAME_FROM | MOUNTEDOVER) > 0;
+ }
+};
+
+pub const port_event = extern struct {
+ events: u32,
+ /// Event source.
+ source: u16,
+ __pad: u16,
+ /// Source-specific object.
+ object: ?*c_void,
+ /// User cookie.
+ cookie: ?*c_void,
+};
+
+pub const port_notify = extern struct {
+ /// Bind request(s) to port.
+ port: u32,
+ /// User defined variable.
+ user: ?*void,
+};
+
+pub const file_obj = extern struct {
+ /// Access time.
+ atim: timespec,
+ /// Modification time
+ mtim: timespec,
+ /// Change time
+ ctim: timespec,
+ __pad: [3]usize,
+ name: [*:0]u8,
+};
+
+// struct ifreq is marked obsolete, with struct lifreq prefered for interface requests.
+// Here we alias lifreq to ifreq to avoid chainging existing code in os and x.os.IPv6.
+pub const SIOCGLIFINDEX = IOWR('i', 133, lifreq);
+pub const SIOCGIFINDEX = SIOCGLIFINDEX;
+pub const MAX_HDW_LEN = 64;
+pub const IFNAMESIZE = 32;
+
+pub const lif_nd_req = extern struct {
+ addr: sockaddr.storage,
+ state_create: u8,
+ state_same_lla: u8,
+ state_diff_lla: u8,
+ hdw_len: i32,
+ flags: i32,
+ __pad: i32,
+ hdw_addr: [MAX_HDW_LEN]u8,
+};
+
+pub const lif_ifinfo_req = extern struct {
+ maxhops: u8,
+ reachtime: u32,
+ reachretrans: u32,
+ maxmtu: u32,
+};
+
+/// IP interface request. See if_tcp(7p) for more info.
+pub const lifreq = extern struct {
+ // Not actually in a union, but the stdlib expects one for ifreq
+ ifrn: extern union {
+ /// Interface name, e.g. "lo0", "en0".
+ name: [IFNAMESIZE]u8,
+ },
+ ru1: extern union {
+ /// For subnet/token etc.
+ addrlen: i32,
+ /// Driver's PPA (physical point of attachment).
+ ppa: u32,
+ },
+ /// One of the IFT types, e.g. IFT_ETHER.
+ @"type": u32,
+ ifru: extern union {
+ /// Address.
+ addr: sockaddr.storage,
+ /// Other end of a peer-to-peer link.
+ dstaddr: sockaddr.storage,
+ /// Broadcast address.
+ broadaddr: sockaddr.storage,
+ /// Address token.
+ token: sockaddr.storage,
+ /// Subnet prefix.
+ subnet: sockaddr.storage,
+ /// Interface index.
+ ivalue: i32,
+ /// Flags for SIOC?LIFFLAGS.
+ flags: u64,
+ /// Hop count metric
+ metric: i32,
+ /// Maximum transmission unit
+ mtu: u32,
+ // Technically [2]i32
+ muxid: packed struct { ip: i32, arp: i32 },
+ /// Neighbor reachability determination entries
+ nd_req: lif_nd_req,
+ /// Link info
+ ifinfo_req: lif_ifinfo_req,
+ /// Name of the multipath interface group
+ groupname: [IFNAMESIZE]u8,
+ binding: [IFNAMESIZE]u8,
+ /// Zone id associated with this interface.
+ zoneid: zoneid_t,
+ /// Duplicate address detection state. Either in progress or completed.
+ dadstate: u32,
+ },
+};
+
+pub const ifreq = lifreq;
+
+const IoCtlCommand = enum(u32) {
+ none = 0x20000000, // no parameters
+ write = 0x40000000, // copy out parameters
+ read = 0x80000000, // copy in parameters
+ read_write = 0xc0000000,
+};
+
+fn ioImpl(cmd: IoCtlCommand, io_type: u8, nr: u8, comptime IOT: type) i32 {
+ const size = @intCast(u32, @truncate(u8, @sizeOf(IOT))) << 16;
+ const t = @intCast(u32, io_type) << 8;
+ return @bitCast(i32, @enumToInt(cmd) | size | t | nr);
+}
+
+pub fn IO(io_type: u8, nr: u8) i32 {
+ return ioImpl(.none, io_type, nr, void);
+}
+
+pub fn IOR(io_type: u8, nr: u8, comptime IOT: type) i32 {
+ return ioImpl(.write, io_type, nr, IOT);
+}
+
+pub fn IOW(io_type: u8, nr: u8, comptime IOT: type) i32 {
+ return ioImpl(.read, io_type, nr, IOT);
+}
+
+pub fn IOWR(io_type: u8, nr: u8, comptime IOT: type) i32 {
+ return ioImpl(.read_write, io_type, nr, IOT);
+}
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
index 8517703566..6e2cef1f0c 100644
--- a/lib/std/debug.zig
+++ b/lib/std/debug.zig
@@ -654,6 +654,7 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo {
.openbsd,
.macos,
.windows,
+ .solaris,
=> return DebugInfo.init(allocator),
else => return error.UnsupportedDebugInfo,
}
@@ -1420,7 +1421,7 @@ pub const ModuleDebugInfo = switch (native_os) {
};
}
},
- .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku => struct {
+ .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris => struct {
base_address: usize,
dwarf: DW.DwarfInfo,
mapped_memory: []const u8,
@@ -1468,7 +1469,7 @@ fn getDebugInfoAllocator() *mem.Allocator {
/// Whether or not the current target can print useful debug information when a segfault occurs.
pub const have_segfault_handling_support = switch (native_os) {
- .linux, .netbsd => true,
+ .linux, .netbsd, .solaris => true,
.windows => true,
.freebsd, .openbsd => @hasDecl(os.system, "ucontext_t"),
else => false,
@@ -1535,6 +1536,7 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_v
.freebsd => @ptrToInt(info.addr),
.netbsd => @ptrToInt(info.info.reason.fault.addr),
.openbsd => @ptrToInt(info.data.fault.addr),
+ .solaris => @ptrToInt(info.reason.fault.addr),
else => unreachable,
};
@@ -1559,13 +1561,13 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_v
.x86_64 => {
const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
const ip = switch (native_os) {
- .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]),
+ .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]),
.freebsd => @intCast(usize, ctx.mcontext.rip),
.openbsd => @intCast(usize, ctx.sc_rip),
else => unreachable,
};
const bp = switch (native_os) {
- .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]),
+ .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]),
.openbsd => @intCast(usize, ctx.sc_rbp),
.freebsd => @intCast(usize, ctx.mcontext.rbp),
else => unreachable,
diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig
index fb4cefed0a..91c66503f1 100644
--- a/lib/std/dynamic_library.zig
+++ b/lib/std/dynamic_library.zig
@@ -14,7 +14,7 @@ const max = std.math.max;
pub const DynLib = switch (builtin.os.tag) {
.linux => if (builtin.link_libc) DlDynlib else ElfDynLib,
.windows => WindowsDynLib,
- .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly => DlDynlib,
+ .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris => DlDynlib,
else => void,
};
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index 66eb1d6642..7aa5dd3976 100644
--- a/lib/std/fs.zig
+++ b/lib/std/fs.zig
@@ -35,7 +35,7 @@ pub const Watch = @import("fs/watch.zig").Watch;
/// fit into a UTF-8 encoded array of this length.
/// The byte count includes room for a null sentinel byte.
pub const MAX_PATH_BYTES = switch (builtin.os.tag) {
- .linux, .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .haiku => os.PATH_MAX,
+ .linux, .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .haiku, .solaris => os.PATH_MAX,
// Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
// If it would require 4 UTF-8 bytes, then there would be a surrogate
// pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
@@ -298,10 +298,10 @@ pub const Dir = struct {
pub const Kind = File.Kind;
};
- const IteratorError = error{AccessDenied} || os.UnexpectedError;
+ const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError;
pub const Iterator = switch (builtin.os.tag) {
- .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => struct {
+ .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => struct {
dir: Dir,
seek: i64,
buf: [8192]u8, // TODO align(@alignOf(os.system.dirent)),
@@ -318,6 +318,7 @@ pub const Dir = struct {
switch (builtin.os.tag) {
.macos, .ios => return self.nextDarwin(),
.freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
+ .solaris => return self.nextSolaris(),
else => @compileError("unimplemented"),
}
}
@@ -372,6 +373,60 @@ pub const Dir = struct {
}
}
+ fn nextSolaris(self: *Self) !?Entry {
+ start_over: while (true) {
+ if (self.index >= self.end_index) {
+ const rc = os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
+ switch (os.errno(rc)) {
+ .SUCCESS => {},
+ .BADF => unreachable, // Dir is invalid or was opened without iteration ability
+ .FAULT => unreachable,
+ .NOTDIR => unreachable,
+ .INVAL => unreachable,
+ else => |err| return os.unexpectedErrno(err),
+ }
+ if (rc == 0) return null;
+ self.index = 0;
+ self.end_index = @intCast(usize, rc);
+ }
+ const entry = @ptrCast(*align(1) os.system.dirent, &self.buf[self.index]);
+ const next_index = self.index + entry.reclen();
+ self.index = next_index;
+
+ const name = mem.spanZ(@ptrCast([*:0]u8, &entry.d_name));
+ if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
+ continue :start_over;
+
+ // Solaris dirent doesn't expose d_type, so we have to call stat to get it.
+ const stat_info = os.fstatat(
+ self.dir.fd,
+ name,
+ os.AT.SYMLINK_NOFOLLOW,
+ ) catch |err| switch (err) {
+ error.NameTooLong => unreachable,
+ error.SymLinkLoop => unreachable,
+ error.FileNotFound => unreachable, // lost the race
+ else => |e| return e,
+ };
+ const entry_kind = switch (stat_info.mode & os.S.IFMT) {
+ os.S.IFIFO => Entry.Kind.NamedPipe,
+ os.S.IFCHR => Entry.Kind.CharacterDevice,
+ os.S.IFDIR => Entry.Kind.Directory,
+ os.S.IFBLK => Entry.Kind.BlockDevice,
+ os.S.IFREG => Entry.Kind.File,
+ os.S.IFLNK => Entry.Kind.SymLink,
+ os.S.IFSOCK => Entry.Kind.UnixDomainSocket,
+ os.S.IFDOOR => Entry.Kind.Door,
+ os.S.IFPORT => Entry.Kind.EventPort,
+ else => Entry.Kind.Unknown,
+ };
+ return Entry{
+ .name = name,
+ .kind = entry_kind,
+ };
+ }
+ }
+
fn nextBsd(self: *Self) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
@@ -704,6 +759,7 @@ pub const Dir = struct {
.netbsd,
.dragonfly,
.openbsd,
+ .solaris,
=> return Iterator{
.dir = self,
.seek = 0,
@@ -1556,7 +1612,7 @@ pub const Dir = struct {
error.AccessDenied => |e| switch (builtin.os.tag) {
// non-Linux POSIX systems return EPERM when trying to delete a directory, so
// we need to handle that case specifically and translate the error
- .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => {
+ .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => {
// Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them)
const fstat = os.fstatatZ(self.fd, sub_path_c, os.AT.SYMLINK_NOFOLLOW) catch return e;
const is_dir = fstat.mode & os.S.IFMT == os.S.IFDIR;
@@ -2441,6 +2497,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
}
switch (builtin.os.tag) {
.linux => return os.readlinkZ("/proc/self/exe", out_buffer),
+ .solaris => return os.readlinkZ("/proc/self/path/a.out", out_buffer),
.freebsd, .dragonfly => {
var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 };
var out_len: usize = out_buffer.len;
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
index d08b743919..fd0492f0bf 100644
--- a/lib/std/fs/file.zig
+++ b/lib/std/fs/file.zig
@@ -41,6 +41,8 @@ pub const File = struct {
File,
UnixDomainSocket,
Whiteout,
+ Door,
+ EventPort,
Unknown,
};
@@ -320,28 +322,40 @@ pub const File = struct {
const atime = st.atime();
const mtime = st.mtime();
const ctime = st.ctime();
+ const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) {
+ .BLOCK_DEVICE => Kind.BlockDevice,
+ .CHARACTER_DEVICE => Kind.CharacterDevice,
+ .DIRECTORY => Kind.Directory,
+ .SYMBOLIC_LINK => Kind.SymLink,
+ .REGULAR_FILE => Kind.File,
+ .SOCKET_STREAM, .SOCKET_DGRAM => Kind.UnixDomainSocket,
+ else => Kind.Unknown,
+ } else blk: {
+ const m = st.mode & os.S.IFMT;
+ switch (m) {
+ os.S.IFBLK => break :blk Kind.BlockDevice,
+ os.S.IFCHR => break :blk Kind.CharacterDevice,
+ os.S.IFDIR => break :blk Kind.Directory,
+ os.S.IFIFO => break :blk Kind.NamedPipe,
+ os.S.IFLNK => break :blk Kind.SymLink,
+ os.S.IFREG => break :blk Kind.File,
+ os.S.IFSOCK => break :blk Kind.UnixDomainSocket,
+ else => {},
+ }
+ if (builtin.os.tag == .solaris) switch (m) {
+ os.S.IFDOOR => break :blk Kind.Door,
+ os.S.IFPORT => break :blk Kind.EventPort,
+ else => {},
+ };
+
+ break :blk .Unknown;
+ };
+
return Stat{
.inode = st.ino,
.size = @bitCast(u64, st.size),
.mode = st.mode,
- .kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) {
- .BLOCK_DEVICE => Kind.BlockDevice,
- .CHARACTER_DEVICE => Kind.CharacterDevice,
- .DIRECTORY => Kind.Directory,
- .SYMBOLIC_LINK => Kind.SymLink,
- .REGULAR_FILE => Kind.File,
- .SOCKET_STREAM, .SOCKET_DGRAM => Kind.UnixDomainSocket,
- else => Kind.Unknown,
- } else switch (st.mode & os.S.IFMT) {
- os.S.IFBLK => Kind.BlockDevice,
- os.S.IFCHR => Kind.CharacterDevice,
- os.S.IFDIR => Kind.Directory,
- os.S.IFIFO => Kind.NamedPipe,
- os.S.IFLNK => Kind.SymLink,
- os.S.IFREG => Kind.File,
- os.S.IFSOCK => Kind.UnixDomainSocket,
- else => Kind.Unknown,
- },
+ .kind = kind,
.atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
.mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
.ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig
index fed1c85f39..491b6fe824 100644
--- a/lib/std/fs/get_app_data_dir.zig
+++ b/lib/std/fs/get_app_data_dir.zig
@@ -44,7 +44,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD
};
return fs.path.join(allocator, &[_][]const u8{ home_dir, "Library", "Application Support", appname });
},
- .linux, .freebsd, .netbsd, .dragonfly, .openbsd => {
+ .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => {
const home_dir = os.getenv("HOME") orelse {
// TODO look in /etc/passwd
return error.AppDataDirUnavailable;
diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig
index 0ce416454b..a6e1e440fd 100644
--- a/lib/std/fs/test.zig
+++ b/lib/std/fs/test.zig
@@ -188,7 +188,7 @@ fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool {
test "Dir.realpath smoke test" {
switch (builtin.os.tag) {
- .linux, .windows, .macos, .ios, .watchos, .tvos => {},
+ .linux, .windows, .macos, .ios, .watchos, .tvos, .solaris => {},
else => return error.SkipZigTest,
}
diff --git a/lib/std/os.zig b/lib/std/os.zig
index 5a4828286d..d85815dde0 100644
--- a/lib/std/os.zig
+++ b/lib/std/os.zig
@@ -31,6 +31,7 @@ pub const freebsd = std.c;
pub const haiku = std.c;
pub const netbsd = std.c;
pub const openbsd = std.c;
+pub const solaris = std.c;
pub const linux = @import("os/linux.zig");
pub const uefi = @import("os/uefi.zig");
pub const wasi = @import("os/wasi.zig");
@@ -64,8 +65,10 @@ else switch (builtin.os.tag) {
};
pub const AF = system.AF;
+pub const AF_SUN = system.AF_SUN;
pub const ARCH = system.ARCH;
pub const AT = system.AT;
+pub const AT_SUN = system.AT_SUN;
pub const CLOCK = system.CLOCK;
pub const CPU_COUNT = system.CPU_COUNT;
pub const CTL = system.CTL;
@@ -101,6 +104,7 @@ pub const RR = system.RR;
pub const S = system.S;
pub const SA = system.SA;
pub const SC = system.SC;
+pub const _SC = system._SC;
pub const SEEK = system.SEEK;
pub const SHUT = system.SHUT;
pub const SIG = system.SIG;
@@ -143,6 +147,10 @@ pub const off_t = system.off_t;
pub const oflags_t = system.oflags_t;
pub const pid_t = system.pid_t;
pub const pollfd = system.pollfd;
+pub const port_t = system.port_t;
+pub const port_event = system.port_event;
+pub const port_notify = system.port_notify;
+pub const file_obj = system.file_obj;
pub const rights_t = system.rights_t;
pub const rlim_t = system.rlim_t;
pub const rlimit = system.rlimit;
@@ -2038,6 +2046,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
.NOTDIR => return error.NotDir,
.NOMEM => return error.SystemResources,
.ROFS => return error.ReadOnlyFileSystem,
+ .EXIST => return error.DirNotEmpty,
.NOTEMPTY => return error.DirNotEmpty,
.INVAL => unreachable, // invalid flags, or pathname has . as last component
@@ -4667,6 +4676,16 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
};
return target;
},
+ .solaris => {
+ var procfs_buf: ["/proc/self/path/-2147483648".len:0]u8 = undefined;
+ const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/path/{d}", .{fd}) catch unreachable;
+
+ const target = readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
+ error.UnsupportedReparsePointType => unreachable,
+ else => |e| return e,
+ };
+ return target;
+ },
else => @compileError("querying for canonical path of a handle is unsupported on this host"),
}
}
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
index 3a0187f735..d3c8d13bd1 100644
--- a/lib/std/os/test.zig
+++ b/lib/std/os/test.zig
@@ -188,7 +188,10 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
}
test "link with relative paths" {
- if (native_os != .linux) return error.SkipZigTest;
+ switch (native_os) {
+ .linux, .solaris => {},
+ else => return error.SkipZigTest,
+ }
var cwd = fs.cwd();
cwd.deleteFile("example.txt") catch {};
@@ -222,7 +225,10 @@ test "link with relative paths" {
}
test "linkat with different directories" {
- if (native_os != .linux) return error.SkipZigTest;
+ switch (native_os) {
+ .linux, .solaris => {},
+ else => return error.SkipZigTest,
+ }
var cwd = fs.cwd();
var tmp = tmpDir(.{});
@@ -634,8 +640,10 @@ test "fcntl" {
}
test "signalfd" {
- if (native_os != .linux)
- return error.SkipZigTest;
+ switch (native_os) {
+ .linux, .solaris => {},
+ else => return error.SkipZigTest,
+ }
_ = std.os.signalfd;
}
@@ -658,8 +666,10 @@ test "sync" {
}
test "fsync" {
- if (native_os != .linux and native_os != .windows)
- return error.SkipZigTest;
+ switch (native_os) {
+ .linux, .windows, .solaris => {},
+ else => return error.SkipZigTest,
+ }
var tmp = tmpDir(.{});
defer tmp.cleanup();
@@ -754,7 +764,10 @@ test "sigaction" {
}
test "dup & dup2" {
- if (native_os != .linux) return error.SkipZigTest;
+ switch (native_os) {
+ .linux, .solaris => {},
+ else => return error.SkipZigTest,
+ }
var tmp = tmpDir(.{});
defer tmp.cleanup();
diff --git a/lib/std/process.zig b/lib/std/process.zig
index f8b986d695..8dce2462a2 100644
--- a/lib/std/process.zig
+++ b/lib/std/process.zig
@@ -625,7 +625,7 @@ pub const UserInfo = struct {
/// POSIX function which gets a uid from username.
pub fn getUserInfo(name: []const u8) !UserInfo {
return switch (builtin.os.tag) {
- .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd, .openbsd, .haiku => posixGetUserInfo(name),
+ .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd, .openbsd, .haiku, .solaris => posixGetUserInfo(name),
else => @compileError("Unsupported OS"),
};
}
@@ -753,6 +753,7 @@ pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0]
.netbsd,
.dragonfly,
.openbsd,
+ .solaris,
=> {
var paths = List.init(allocator);
errdefer {
diff --git a/lib/std/target.zig b/lib/std/target.zig
index c94b7a4386..65c9b91839 100644
--- a/lib/std/target.zig
+++ b/lib/std/target.zig
@@ -235,7 +235,6 @@ pub const Target = struct {
.fuchsia,
.kfreebsd,
.lv2,
- .solaris,
.zos,
.haiku,
.minix,
@@ -310,6 +309,12 @@ pub const Target = struct {
.max = .{ .major = 6, .minor = 0 },
},
},
+ .solaris => return .{
+ .semver = .{
+ .min = .{ .major = 5, .minor = 11 },
+ .max = .{ .major = 5, .minor = 11 },
+ },
+ },
.linux => return .{
.linux = .{
@@ -353,6 +358,7 @@ pub const Target = struct {
.netbsd,
.openbsd,
.dragonfly,
+ .solaris,
=> return TaggedVersionRange{ .semver = self.version_range.semver },
else => return .none,
@@ -385,6 +391,7 @@ pub const Target = struct {
.dragonfly,
.openbsd,
.haiku,
+ .solaris,
=> true,
.linux,
@@ -395,7 +402,6 @@ pub const Target = struct {
.fuchsia,
.kfreebsd,
.lv2,
- .solaris,
.zos,
.minix,
.rtems,
@@ -1516,6 +1522,7 @@ pub const Target = struct {
.netbsd => return copy(&result, "/libexec/ld.elf_so"),
.openbsd => return copy(&result, "/usr/libexec/ld.so"),
.dragonfly => return copy(&result, "/libexec/ld-elf.so.2"),
+ .solaris => return copy(&result, "/lib/64/ld.so.1"),
.linux => switch (self.cpu.arch) {
.i386,
.sparc,
@@ -1634,7 +1641,6 @@ pub const Target = struct {
.fuchsia,
.kfreebsd,
.lv2,
- .solaris,
.zos,
.minix,
.rtems,
diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig
index da4057d6e1..a86d6a2d45 100644
--- a/lib/std/zig/system.zig
+++ b/lib/std/zig/system.zig
@@ -101,6 +101,17 @@ pub const NativePaths = struct {
return self;
}
+ if (comptime native_target.os.tag == .solaris) {
+ try self.addLibDir("/usr/lib/64");
+ try self.addLibDir("/usr/local/lib/64");
+ try self.addLibDir("/lib/64");
+
+ try self.addIncludeDir("/usr/include");
+ try self.addIncludeDir("/usr/local/include");
+
+ return self;
+ }
+
if (native_target.os.tag != .windows) {
const triple = try native_target.linuxTriple(allocator);
const qual = native_target.cpu.arch.ptrBitWidth();
@@ -243,6 +254,18 @@ pub const NativeTargetInfo = struct {
error.InvalidVersion => {},
}
},
+ .solaris => {
+ const uts = std.os.uname();
+ const release = mem.spanZ(&uts.release);
+ if (std.builtin.Version.parse(release)) |ver| {
+ os.version_range.semver.min = ver;
+ os.version_range.semver.max = ver;
+ } else |err| switch (err) {
+ error.Overflow => {},
+ error.InvalidCharacter => {},
+ error.InvalidVersion => {},
+ }
+ },
.windows => {
const detected_version = windows.detectRuntimeVersion();
os.version_range.windows.min = detected_version;
diff --git a/src/libc_installation.zig b/src/libc_installation.zig
index 62174930f8..d54af71415 100644
--- a/src/libc_installation.zig
+++ b/src/libc_installation.zig
@@ -221,6 +221,7 @@ pub const LibCInstallation = struct {
batch.add(&async self.findNativeIncludeDirPosix(args));
switch (Target.current.os.tag) {
.freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try std.mem.dupeZ(args.allocator, u8, "/usr/lib"),
+ .solaris => self.crt_dir = try std.mem.dupeZ(args.allocator, u8, "/usr/lib/64"),
.linux => batch.add(&async self.findNativeCrtDirPosix(args)),
else => {},
}
diff --git a/src/link/Elf.zig b/src/link/Elf.zig
index ec047ced35..6c3220d39e 100644
--- a/src/link/Elf.zig
+++ b/src/link/Elf.zig
@@ -3446,6 +3446,15 @@ const CsuObjects = struct {
.static_pie => result.set( "start_dyn.o", "crti.o", "crtbeginS.o", "crtendS.o", "crtn.o" ),
// zig fmt: on
},
+ .solaris => switch (mode) {
+ // zig fmt: off
+ .dynamic_lib => result.set( null, "crti.o", null, null, "crtn.o" ),
+ .dynamic_exe,
+ .dynamic_pie => result.set( "crt1.o", "crti.o", null, null, "crtn.o" ),
+ .static_exe,
+ .static_pie => result.set( null, null, null, null, null ),
+ // zig fmt: on
+ },
else => {},
}
}
diff --git a/src/stage1/os.hpp b/src/stage1/os.hpp
index f1022778d4..6d086c8901 100644
--- a/src/stage1/os.hpp
+++ b/src/stage1/os.hpp
@@ -33,6 +33,8 @@
#define ZIG_OS_OPENBSD
#elif defined(__HAIKU__)
#define ZIG_OS_HAIKU
+#elif defined(__sun)
+#define ZIG_OS_SOLARIS
#else
#define ZIG_OS_UNKNOWN
#endif
diff --git a/src/stage1/target.cpp b/src/stage1/target.cpp
index 8415b1934e..4da19e4c36 100644
--- a/src/stage1/target.cpp
+++ b/src/stage1/target.cpp
@@ -399,6 +399,9 @@ Error target_parse_os(Os *out_os, const char *os_ptr, size_t os_len) {
#elif defined(ZIG_OS_HAIKU)
*out_os = OsHaiku;
return ErrorNone;
+#elif defined(ZIG_OS_SOLARIS)
+ *out_os = OsSolaris;
+ return ErrorNone;
#else
zig_panic("stage1 is unable to detect native target for this OS");
#endif
@@ -670,6 +673,7 @@ uint32_t target_c_type_size_in_bits(const ZigTarget *target, CIntType id) {
case OsOpenBSD:
case OsWASI:
case OsHaiku:
+ case OsSolaris:
case OsEmscripten:
case OsPlan9:
switch (id) {
@@ -728,7 +732,6 @@ uint32_t target_c_type_size_in_bits(const ZigTarget *target, CIntType id) {
case OsCloudABI:
case OsKFreeBSD:
case OsLv2:
- case OsSolaris:
case OsZOS:
case OsMinix:
case OsRTEMS:
diff --git a/src/target.zig b/src/target.zig
index 09e65ff909..621335209c 100644
--- a/src/target.zig
+++ b/src/target.zig
@@ -433,6 +433,13 @@ pub fn libcFullLinkFlags(target: std.Target) []const []const u8 {
"-lc",
"-lutil",
},
+ .solaris => &[_][]const u8{
+ "-lm",
+ "-lsocket",
+ "-lnsl",
+ // Solaris releases after 10 merged the threading libraries into libc.
+ "-lc",
+ },
.haiku => &[_][]const u8{
"-lm",
"-lroot",
diff --git a/src/type.zig b/src/type.zig
index 4a0f2a0536..a2da252f61 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -3800,6 +3800,7 @@ pub const CType = enum {
.wasi,
.emscripten,
.plan9,
+ .solaris,
=> switch (self) {
.short,
.ushort,
@@ -3851,7 +3852,6 @@ pub const CType = enum {
.fuchsia,
.kfreebsd,
.lv2,
- .solaris,
.zos,
.haiku,
.minix,
From 42aa1ea115eca3dcc704eddf020ce87271a41174 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Fri, 24 Sep 2021 17:33:06 -0700
Subject: [PATCH 116/160] stage2: implement `@memset` and `@memcpy` builtins
---
src/Air.zig | 15 ++++
src/AstGen.zig | 16 ++---
src/Liveness.zig | 5 ++
src/Sema.zig | 126 ++++++++++++++++++++++++++++++++--
src/codegen.zig | 12 ++++
src/codegen/c.zig | 46 ++++++++++++-
src/codegen/llvm.zig | 53 ++++++++++++++
src/codegen/llvm/bindings.zig | 19 +++++
src/link/C/zig.h | 1 +
src/print_air.zig | 24 +++++++
src/print_zir.zig | 30 +++++++-
src/type.zig | 73 +++++++++++++++++---
test/behavior/basic.zig | 18 +++++
test/behavior/misc.zig | 10 ---
14 files changed, 412 insertions(+), 36 deletions(-)
diff --git a/src/Air.zig b/src/Air.zig
index c3181fac60..4341271f3a 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -321,6 +321,19 @@ pub const Inst = struct {
/// Uses the `ty_op` field.
int_to_float,
+ /// Given dest ptr, value, and len, set all elements at dest to value.
+ /// Result type is always void.
+ /// Uses the `pl_op` field. Operand is the dest ptr. Payload is `Bin`. `lhs` is the
+ /// value, `rhs` is the length.
+ /// The element type may be any type, not just u8.
+ memset,
+ /// Given dest ptr, src ptr, and len, copy len elements from src to dest.
+ /// Result type is always void.
+ /// Uses the `pl_op` field. Operand is the dest ptr. Payload is `Bin`. `lhs` is the
+ /// src ptr, `rhs` is the length.
+ /// The element type may be any type, not just u8.
+ memcpy,
+
/// Uses the `ty_pl` field with payload `Cmpxchg`.
cmpxchg_weak,
/// Uses the `ty_pl` field with payload `Cmpxchg`.
@@ -628,6 +641,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.atomic_store_monotonic,
.atomic_store_release,
.atomic_store_seq_cst,
+ .memset,
+ .memcpy,
=> return Type.initTag(.void),
.ptrtoint,
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 416584bee9..469e77037a 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -2149,8 +2149,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.field_ptr_type,
.field_parent_ptr,
.maximum,
- .memcpy,
- .memset,
.minimum,
.builtin_async_call,
.c_import,
@@ -2204,6 +2202,8 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.set_float_mode,
.set_runtime_safety,
.closure_capture,
+ .memcpy,
+ .memset,
=> break :b true,
}
} else switch (maybe_unused_result) {
@@ -7576,17 +7576,17 @@ fn builtinCall(
},
.memcpy => {
const result = try gz.addPlNode(.memcpy, node, Zir.Inst.Memcpy{
- .dest = try expr(gz, scope, .{ .ty = .manyptr_u8_type }, params[0]),
- .source = try expr(gz, scope, .{ .ty = .manyptr_const_u8_type }, params[1]),
- .byte_count = try expr(gz, scope, .{ .ty = .usize_type }, params[2]),
+ .dest = try expr(gz, scope, .{ .coerced_ty = .manyptr_u8_type }, params[0]),
+ .source = try expr(gz, scope, .{ .coerced_ty = .manyptr_const_u8_type }, params[1]),
+ .byte_count = try expr(gz, scope, .{ .coerced_ty = .usize_type }, params[2]),
});
return rvalue(gz, rl, result, node);
},
.memset => {
const result = try gz.addPlNode(.memset, node, Zir.Inst.Memset{
- .dest = try expr(gz, scope, .{ .ty = .manyptr_u8_type }, params[0]),
- .byte = try expr(gz, scope, .{ .ty = .u8_type }, params[1]),
- .byte_count = try expr(gz, scope, .{ .ty = .usize_type }, params[2]),
+ .dest = try expr(gz, scope, .{ .coerced_ty = .manyptr_u8_type }, params[0]),
+ .byte = try expr(gz, scope, .{ .coerced_ty = .u8_type }, params[1]),
+ .byte_count = try expr(gz, scope, .{ .coerced_ty = .usize_type }, params[2]),
});
return rvalue(gz, rl, result, node);
},
diff --git a/src/Liveness.zig b/src/Liveness.zig
index 4da5eaa284..42ab1ab351 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -361,6 +361,11 @@ fn analyzeInst(
const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data;
return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none });
},
+ .memset, .memcpy => {
+ const pl_op = inst_datas[inst].pl_op;
+ const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
+ return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs });
+ },
.br => {
const br = inst_datas[inst].br;
return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none });
diff --git a/src/Sema.zig b/src/Sema.zig
index 87cde2ca1a..41fabbfacd 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -341,8 +341,6 @@ pub fn analyzeBody(
.field_ptr_type => try sema.zirFieldPtrType(block, inst),
.field_parent_ptr => try sema.zirFieldParentPtr(block, inst),
.maximum => try sema.zirMaximum(block, inst),
- .memcpy => try sema.zirMemcpy(block, inst),
- .memset => try sema.zirMemset(block, inst),
.minimum => try sema.zirMinimum(block, inst),
.builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst),
.@"resume" => try sema.zirResume(block, inst),
@@ -526,6 +524,16 @@ pub fn analyzeBody(
i += 1;
continue;
},
+ .memcpy => {
+ try sema.zirMemcpy(block, inst);
+ i += 1;
+ continue;
+ },
+ .memset => {
+ try sema.zirMemset(block, inst);
+ i += 1;
+ continue;
+ },
// Special case instructions to handle comptime control flow.
.@"break" => {
@@ -8422,16 +8430,119 @@ fn zirMaximum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
return sema.mod.fail(&block.base, src, "TODO: Sema.zirMaximum", .{});
}
-fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(Zir.Inst.Memcpy, inst_data.payload_index).data;
const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemcpy", .{});
+ const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const src_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
+ const dest_ptr = sema.resolveInst(extra.dest);
+ const dest_ptr_ty = sema.typeOf(dest_ptr);
+
+ if (dest_ptr_ty.zigTypeTag() != .Pointer) {
+ return sema.mod.fail(&block.base, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty});
+ }
+ if (dest_ptr_ty.isConstPtr()) {
+ return sema.mod.fail(&block.base, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty});
+ }
+
+ const uncasted_src_ptr = sema.resolveInst(extra.source);
+ const uncasted_src_ptr_ty = sema.typeOf(uncasted_src_ptr);
+ if (uncasted_src_ptr_ty.zigTypeTag() != .Pointer) {
+ return sema.mod.fail(&block.base, src_src, "expected pointer, found '{}'", .{
+ uncasted_src_ptr_ty,
+ });
+ }
+ const src_ptr_info = uncasted_src_ptr_ty.ptrInfo().data;
+ const wanted_src_ptr_ty = try Module.ptrType(
+ sema.arena,
+ dest_ptr_ty.elemType2(),
+ null,
+ src_ptr_info.@"align",
+ src_ptr_info.@"addrspace",
+ 0,
+ 0,
+ false,
+ src_ptr_info.@"allowzero",
+ src_ptr_info.@"volatile",
+ .Many,
+ );
+ const src_ptr = try sema.coerce(block, wanted_src_ptr_ty, uncasted_src_ptr, src_src);
+ const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src);
+
+ const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr);
+ const maybe_src_ptr_val = try sema.resolveDefinedValue(block, src_src, src_ptr);
+ const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len);
+
+ const runtime_src = if (maybe_dest_ptr_val) |dest_ptr_val| rs: {
+ if (maybe_src_ptr_val) |src_ptr_val| {
+ if (maybe_len_val) |len_val| {
+ _ = dest_ptr_val;
+ _ = src_ptr_val;
+ _ = len_val;
+ return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemcpy at comptime", .{});
+ } else break :rs len_src;
+ } else break :rs src_src;
+ } else dest_src;
+
+ try sema.requireRuntimeBlock(block, runtime_src);
+ _ = try block.addInst(.{
+ .tag = .memcpy,
+ .data = .{ .pl_op = .{
+ .operand = dest_ptr,
+ .payload = try sema.addExtra(Air.Bin{
+ .lhs = src_ptr,
+ .rhs = len,
+ }),
+ } },
+ });
}
-fn zirMemset(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirMemset(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const extra = sema.code.extraData(Zir.Inst.Memset, inst_data.payload_index).data;
const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemset", .{});
+ const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const value_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
+ const dest_ptr = sema.resolveInst(extra.dest);
+ const dest_ptr_ty = sema.typeOf(dest_ptr);
+ if (dest_ptr_ty.zigTypeTag() != .Pointer) {
+ return sema.mod.fail(&block.base, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty});
+ }
+ if (dest_ptr_ty.isConstPtr()) {
+ return sema.mod.fail(&block.base, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty});
+ }
+ const elem_ty = dest_ptr_ty.elemType2();
+ const value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.byte), value_src);
+ const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src);
+
+ const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr);
+ const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len);
+
+ const runtime_src = if (maybe_dest_ptr_val) |ptr_val| rs: {
+ if (maybe_len_val) |len_val| {
+ if (try sema.resolveMaybeUndefVal(block, value_src, value)) |val| {
+ _ = ptr_val;
+ _ = len_val;
+ _ = val;
+ return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemset at comptime", .{});
+ } else break :rs value_src;
+ } else break :rs len_src;
+ } else dest_src;
+
+ try sema.requireRuntimeBlock(block, runtime_src);
+ _ = try block.addInst(.{
+ .tag = .memset,
+ .data = .{ .pl_op = .{
+ .operand = dest_ptr,
+ .payload = try sema.addExtra(Air.Bin{
+ .lhs = value,
+ .rhs = len,
+ }),
+ } },
+ });
}
fn zirMinimum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -10090,7 +10201,8 @@ fn coerceArrayPtrToMany(
// The comptime Value representation is compatible with both types.
return sema.addConstant(dest_type, val);
}
- return sema.mod.fail(&block.base, inst_src, "TODO implement coerceArrayPtrToMany runtime instruction", .{});
+ try sema.requireRuntimeBlock(block, inst_src);
+ return sema.bitcast(block, dest_type, inst, inst_src);
}
fn analyzeDeclVal(
diff --git a/src/codegen.zig b/src/codegen.zig
index f812cbc5d4..102f8d4985 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -887,6 +887,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.cmpxchg_weak => try self.airCmpxchg(inst),
.atomic_rmw => try self.airAtomicRmw(inst),
.atomic_load => try self.airAtomicLoad(inst),
+ .memcpy => try self.airMemcpy(inst),
+ .memset => try self.airMemset(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -4883,6 +4885,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch});
}
+ fn airMemset(self: *Self, inst: Air.Inst.Index) !void {
+ _ = inst;
+ return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch});
+ }
+
+ fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void {
+ _ = inst;
+ return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch});
+ }
+
fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue {
// First section of indexes correspond to a set number of constant values.
const ref_int = @enumToInt(inst);
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index a82f0e57f7..16b13db292 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -953,6 +953,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.cmpxchg_strong => try airCmpxchg(f, inst, "strong"),
.atomic_rmw => try airAtomicRmw(f, inst),
.atomic_load => try airAtomicLoad(f, inst),
+ .memset => try airMemset(f, inst),
+ .memcpy => try airMemcpy(f, inst),
.int_to_float,
.float_to_int,
@@ -2005,8 +2007,12 @@ fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue {
fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue {
const atomic_load = f.air.instructions.items(.data)[inst].atomic_load;
- const inst_ty = f.air.typeOfIndex(inst);
const ptr = try f.resolveInst(atomic_load.ptr);
+ const ptr_ty = f.air.typeOf(atomic_load.ptr);
+ if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst))
+ return CValue.none;
+
+ const inst_ty = f.air.typeOfIndex(inst);
const local = try f.allocLocal(inst_ty, .Const);
const writer = f.object.writer();
@@ -2036,6 +2042,44 @@ fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CVa
return local;
}
+fn airMemset(f: *Function, inst: Air.Inst.Index) !CValue {
+ const pl_op = f.air.instructions.items(.data)[inst].pl_op;
+ const extra = f.air.extraData(Air.Bin, pl_op.payload).data;
+ const dest_ptr = try f.resolveInst(pl_op.operand);
+ const value = try f.resolveInst(extra.lhs);
+ const len = try f.resolveInst(extra.rhs);
+ const writer = f.object.writer();
+
+ try writer.writeAll("memset(");
+ try f.writeCValue(writer, dest_ptr);
+ try writer.writeAll(", ");
+ try f.writeCValue(writer, value);
+ try writer.writeAll(", ");
+ try f.writeCValue(writer, len);
+ try writer.writeAll(");\n");
+
+ return CValue.none;
+}
+
+fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue {
+ const pl_op = f.air.instructions.items(.data)[inst].pl_op;
+ const extra = f.air.extraData(Air.Bin, pl_op.payload).data;
+ const dest_ptr = try f.resolveInst(pl_op.operand);
+ const src_ptr = try f.resolveInst(extra.lhs);
+ const len = try f.resolveInst(extra.rhs);
+ const writer = f.object.writer();
+
+ try writer.writeAll("memcpy(");
+ try f.writeCValue(writer, dest_ptr);
+ try writer.writeAll(", ");
+ try f.writeCValue(writer, src_ptr);
+ try writer.writeAll(", ");
+ try f.writeCValue(writer, len);
+ try writer.writeAll(");\n");
+
+ return CValue.none;
+}
+
fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
return switch (order) {
.Unordered => "memory_order_relaxed",
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index b2b87b97d8..b15834c963 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1279,6 +1279,8 @@ pub const FuncGen = struct {
.fence => try self.airFence(inst),
.atomic_rmw => try self.airAtomicRmw(inst),
.atomic_load => try self.airAtomicLoad(inst),
+ .memset => try self.airMemset(inst),
+ .memcpy => try self.airMemcpy(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2426,6 +2428,8 @@ pub const FuncGen = struct {
const atomic_load = self.air.instructions.items(.data)[inst].atomic_load;
const ptr = try self.resolveInst(atomic_load.ptr);
const ptr_ty = self.air.typeOf(atomic_load.ptr);
+ if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst))
+ return null;
const ordering = toLlvmAtomicOrdering(atomic_load.order);
const operand_ty = ptr_ty.elemType();
const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false);
@@ -2468,6 +2472,55 @@ pub const FuncGen = struct {
return null;
}
+ fn airMemset(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
+ const dest_ptr = try self.resolveInst(pl_op.operand);
+ const ptr_ty = self.air.typeOf(pl_op.operand);
+ const value = try self.resolveInst(extra.lhs);
+ const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndef() else false;
+ const len = try self.resolveInst(extra.rhs);
+ const u8_llvm_ty = self.context.intType(8);
+ const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0);
+ const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, "");
+ const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value;
+ const target = self.dg.module.getTarget();
+ const dest_ptr_align = ptr_ty.ptrAlignment(target);
+ const memset = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align);
+ memset.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr()));
+
+ if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) {
+ // TODO generate valgrind client request to mark byte range as undefined
+ // see gen_valgrind_undef() in codegen.cpp
+ }
+ return null;
+ }
+
+ fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
+ const dest_ptr = try self.resolveInst(pl_op.operand);
+ const dest_ptr_ty = self.air.typeOf(pl_op.operand);
+ const src_ptr = try self.resolveInst(extra.lhs);
+ const src_ptr_ty = self.air.typeOf(extra.lhs);
+ const len = try self.resolveInst(extra.rhs);
+ const u8_llvm_ty = self.context.intType(8);
+ const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0);
+ const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, "");
+ const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, "");
+ const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr();
+ const target = self.dg.module.getTarget();
+ const memcpy = self.builder.buildMemCpy(
+ dest_ptr_u8,
+ dest_ptr_ty.ptrAlignment(target),
+ src_ptr_u8,
+ src_ptr_ty.ptrAlignment(target),
+ len,
+ );
+ memcpy.setVolatile(llvm.Bool.fromBool(is_volatile));
+ return null;
+ }
+
fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value {
const id = llvm.lookupIntrinsicID(name.ptr, name.len);
assert(id != 0);
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index c53ac08fdd..9d32682260 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -632,6 +632,25 @@ pub const Builder = opaque {
DestTy: *const Type,
Name: [*:0]const u8,
) *const Value;
+
+ pub const buildMemSet = LLVMBuildMemSet;
+ extern fn LLVMBuildMemSet(
+ B: *const Builder,
+ Ptr: *const Value,
+ Val: *const Value,
+ Len: *const Value,
+ Align: c_uint,
+ ) *const Value;
+
+ pub const buildMemCpy = LLVMBuildMemCpy;
+ extern fn LLVMBuildMemCpy(
+ B: *const Builder,
+ Dst: *const Value,
+ DstAlign: c_uint,
+ Src: *const Value,
+ SrcAlign: c_uint,
+ Size: *const Value,
+ ) *const Value;
};
pub const IntPredicate = enum(c_uint) {
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index e19a138c1b..b34068d1f2 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -126,6 +126,7 @@
#define int128_t __int128
#define uint128_t unsigned __int128
ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t);
+ZIG_EXTERN_C void *memset (void *, int, size_t);
static inline uint8_t zig_addw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) {
uint8_t thresh = max - rhs;
diff --git a/src/print_air.zig b/src/print_air.zig
index a9ad993eb0..fa384baae0 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -202,6 +202,8 @@ const Writer = struct {
.atomic_store_release => try w.writeAtomicStore(s, inst, .Release),
.atomic_store_seq_cst => try w.writeAtomicStore(s, inst, .SeqCst),
.atomic_rmw => try w.writeAtomicRmw(s, inst),
+ .memcpy => try w.writeMemcpy(s, inst),
+ .memset => try w.writeMemset(s, inst),
}
}
@@ -322,6 +324,28 @@ const Writer = struct {
try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) });
}
+ fn writeMemset(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
+ const pl_op = w.air.instructions.items(.data)[inst].pl_op;
+ const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
+
+ try w.writeOperand(s, inst, 0, pl_op.operand);
+ try s.writeAll(", ");
+ try w.writeOperand(s, inst, 1, extra.lhs);
+ try s.writeAll(", ");
+ try w.writeOperand(s, inst, 2, extra.rhs);
+ }
+
+ fn writeMemcpy(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
+ const pl_op = w.air.instructions.items(.data)[inst].pl_op;
+ const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
+
+ try w.writeOperand(s, inst, 0, pl_op.operand);
+ try s.writeAll(", ");
+ try w.writeOperand(s, inst, 1, extra.lhs);
+ try s.writeAll(", ");
+ try w.writeOperand(s, inst, 2, extra.rhs);
+ }
+
fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
const val = w.air.values[ty_pl.payload];
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 9350fd0de3..6ae218ed22 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -210,8 +210,6 @@ const Writer = struct {
.mul_add,
.builtin_call,
.field_parent_ptr,
- .memcpy,
- .memset,
.builtin_async_call,
=> try self.writePlNode(stream, inst),
@@ -222,6 +220,8 @@ const Writer = struct {
.cmpxchg_strong, .cmpxchg_weak => try self.writeCmpxchg(stream, inst),
.atomic_store => try self.writeAtomicStore(stream, inst),
.atomic_rmw => try self.writeAtomicRmw(stream, inst),
+ .memcpy => try self.writeMemcpy(stream, inst),
+ .memset => try self.writeMemset(stream, inst),
.struct_init_anon,
.struct_init_anon_ref,
@@ -692,6 +692,32 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
+ fn writeMemcpy(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Zir.Inst.Memcpy, inst_data.payload_index).data;
+
+ try self.writeInstRef(stream, extra.dest);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, extra.source);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, extra.byte_count);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
+
+ fn writeMemset(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Zir.Inst.Memset, inst_data.payload_index).data;
+
+ try self.writeInstRef(stream, extra.dest);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, extra.byte);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, extra.byte_count);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
+
fn writeStructInitAnon(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.StructInitAnon, inst_data.payload_index);
diff --git a/src/type.zig b/src/type.zig
index a2da252f61..0381111345 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -2391,12 +2391,11 @@ pub const Type = extern union {
};
}
- /// Asserts the type is a pointer or array type.
- pub fn elemType(self: Type) Type {
- return switch (self.tag()) {
- .vector => self.castTag(.vector).?.data.elem_type,
- .array => self.castTag(.array).?.data.elem_type,
- .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type,
+ pub fn childType(ty: Type) Type {
+ return switch (ty.tag()) {
+ .vector => ty.castTag(.vector).?.data.elem_type,
+ .array => ty.castTag(.array).?.data.elem_type,
+ .array_sentinel => ty.castTag(.array_sentinel).?.data.elem_type,
.single_const_pointer,
.single_mut_pointer,
.many_const_pointer,
@@ -2405,7 +2404,7 @@ pub const Type = extern union {
.c_mut_pointer,
.const_slice,
.mut_slice,
- => self.castPointer().?.data,
+ => ty.castPointer().?.data,
.array_u8,
.array_u8_sentinel_0,
@@ -2415,12 +2414,70 @@ pub const Type = extern union {
=> Type.initTag(.u8),
.single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
- .pointer => self.castTag(.pointer).?.data.pointee_type,
+ .pointer => ty.castTag(.pointer).?.data.pointee_type,
else => unreachable,
};
}
+ /// Asserts the type is a pointer or array type.
+ /// TODO this is deprecated in favor of `childType`.
+ pub const elemType = childType;
+
+ /// For *[N]T, returns T.
+ /// For ?*T, returns T.
+ /// For ?*[N]T, returns T.
+ /// For ?[*]T, returns T.
+ /// For *T, returns T.
+ /// For [*]T, returns T.
+ pub fn elemType2(ty: Type) Type {
+ return switch (ty.tag()) {
+ .vector => ty.castTag(.vector).?.data.elem_type,
+ .array => ty.castTag(.array).?.data.elem_type,
+ .array_sentinel => ty.castTag(.array_sentinel).?.data.elem_type,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .c_const_pointer,
+ .c_mut_pointer,
+ .const_slice,
+ .mut_slice,
+ => ty.castPointer().?.data,
+
+ .single_const_pointer,
+ .single_mut_pointer,
+ => ty.castPointer().?.data.shallowElemType(),
+
+ .array_u8,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .manyptr_u8,
+ .manyptr_const_u8,
+ => Type.initTag(.u8),
+
+ .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
+ .pointer => {
+ const info = ty.castTag(.pointer).?.data;
+ const child_ty = info.pointee_type;
+ if (info.size == .One) {
+ return child_ty.shallowElemType();
+ } else {
+ return child_ty;
+ }
+ },
+
+ // TODO handle optionals
+
+ else => unreachable,
+ };
+ }
+
+ fn shallowElemType(child_ty: Type) Type {
+ return switch (child_ty.zigTypeTag()) {
+ .Array, .Vector => child_ty.childType(),
+ else => child_ty,
+ };
+ }
+
/// Asserts that the type is an optional.
/// Resulting `Type` will have inner memory referencing `buf`.
pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type {
diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig
index 517162c8d4..f6876e29ad 100644
--- a/test/behavior/basic.zig
+++ b/test/behavior/basic.zig
@@ -170,3 +170,21 @@ test "string concatenation" {
test "array mult operator" {
try expect(mem.eql(u8, "ab" ** 5, "ababababab"));
}
+
+test "memcpy and memset intrinsics" {
+ try testMemcpyMemset();
+ // TODO add comptime test coverage
+ //comptime try testMemcpyMemset();
+}
+
+fn testMemcpyMemset() !void {
+ var foo: [20]u8 = undefined;
+ var bar: [20]u8 = undefined;
+
+ @memset(&foo, 'A', foo.len);
+ @memcpy(&bar, &foo, bar.len);
+
+ try expect(bar[0] == 'A');
+ try expect(bar[11] == 'A');
+ try expect(bar[19] == 'A');
+}
diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig
index 5394e6fd14..9b3bf48366 100644
--- a/test/behavior/misc.zig
+++ b/test/behavior/misc.zig
@@ -5,16 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings;
const mem = std.mem;
const builtin = @import("builtin");
-test "memcpy and memset intrinsics" {
- var foo: [20]u8 = undefined;
- var bar: [20]u8 = undefined;
-
- @memset(&foo, 'A', foo.len);
- @memcpy(&bar, &foo, bar.len);
-
- if (bar[11] != 'A') unreachable;
-}
-
test "slicing" {
var array: [20]i32 = undefined;
From 15f55b2805541276f491d255f60f501c8cbd1191 Mon Sep 17 00:00:00 2001
From: xackus <14938807+xackus@users.noreply.github.com>
Date: Fri, 24 Sep 2021 20:09:31 +0200
Subject: [PATCH 117/160] os.flock: FreeBSD can return EOPNOTSUPP
---
lib/std/fs/file.zig | 2 ++
lib/std/os.zig | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
index fd0492f0bf..a71c9ae0d2 100644
--- a/lib/std/fs/file.zig
+++ b/lib/std/fs/file.zig
@@ -866,6 +866,7 @@ pub const File = struct {
pub const LockError = error{
SystemResources,
+ FileLocksNotSupported,
} || os.UnexpectedError;
/// Blocks when an incompatible lock is held by another process.
@@ -928,6 +929,7 @@ pub const File = struct {
return os.flock(file.handle, os.LOCK.UN) catch |err| switch (err) {
error.WouldBlock => unreachable, // unlocking can't block
error.SystemResources => unreachable, // We are deallocating resources.
+ error.FileLocksNotSupported => unreachable, // We already got the lock.
error.Unexpected => unreachable, // Resource deallocation must succeed.
};
}
diff --git a/lib/std/os.zig b/lib/std/os.zig
index d85815dde0..a4fad9bc20 100644
--- a/lib/std/os.zig
+++ b/lib/std/os.zig
@@ -4501,8 +4501,12 @@ pub const FlockError = error{
/// The kernel ran out of memory for allocating file locks
SystemResources,
+
+ /// The underlying filesystem does not support file locks
+ FileLocksNotSupported,
} || UnexpectedError;
+/// Depending on the operating system `flock` may or may not interact with `fcntl` locks made by other processes.
pub fn flock(fd: fd_t, operation: i32) FlockError!void {
while (true) {
const rc = system.flock(fd, operation);
@@ -4513,6 +4517,7 @@ pub fn flock(fd: fd_t, operation: i32) FlockError!void {
.INVAL => unreachable, // invalid parameters
.NOLCK => return error.SystemResources,
.AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error
+ .OPNOTSUPP => return error.FileLocksNotSupported,
else => |err| return unexpectedErrno(err),
}
}
From 04366576ea4be4959b596ebff7041d17e18d08d8 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Sat, 25 Sep 2021 17:52:50 -0700
Subject: [PATCH 118/160] stage2: implement `@sizeOf` for non-packed structs
---
src/Sema.zig | 4 +---
src/type.zig | 16 +++++++++++++++-
test/behavior/struct.zig | 21 +++++++++++++++++++++
test/behavior/struct_stage1.zig | 17 +----------------
4 files changed, 38 insertions(+), 20 deletions(-)
diff --git a/src/Sema.zig b/src/Sema.zig
index 41fabbfacd..1001b388a9 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -6575,6 +6575,7 @@ fn zirSizeOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro
const src = inst_data.src();
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
const operand_ty = try sema.resolveType(block, operand_src, inst_data.operand);
+ try sema.resolveTypeLayout(block, src, operand_ty);
const target = sema.mod.getTarget();
const abi_size = switch (operand_ty.zigTypeTag()) {
.Fn => unreachable,
@@ -10846,9 +10847,6 @@ pub fn resolveTypeLayout(
ty: Type,
) CompileError!void {
switch (ty.zigTypeTag()) {
- .Pointer => {
- return sema.resolveTypeLayout(block, src, ty.elemType());
- },
.Struct => {
const resolved_ty = try sema.resolveTypeFields(block, src, ty);
const struct_obj = resolved_ty.castTag(.@"struct").?.data;
diff --git a/src/type.zig b/src/type.zig
index 0381111345..bd008e809a 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -1765,7 +1765,21 @@ pub const Type = extern union {
.@"struct" => {
const s = self.castTag(.@"struct").?.data;
assert(s.status == .have_layout);
- @panic("TODO abiSize struct");
+ const is_packed = s.layout == .Packed;
+ if (is_packed) @panic("TODO packed structs");
+ var size: u64 = 0;
+ for (s.fields.values()) |field| {
+ const field_align = a: {
+ if (field.abi_align.tag() == .abi_align_default) {
+ break :a field.ty.abiAlignment(target);
+ } else {
+ break :a field.abi_align.toUnsignedInt();
+ }
+ };
+ size = std.mem.alignForwardGeneric(u64, size, field_align);
+ size += field.ty.abiSize(target);
+ }
+ return size;
},
.enum_simple, .enum_full, .enum_nonexhaustive => {
var buffer: Payload.Bits = undefined;
diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig
index 6f00b71057..e048848799 100644
--- a/test/behavior/struct.zig
+++ b/test/behavior/struct.zig
@@ -31,3 +31,24 @@ test "return empty struct instance" {
fn returnEmptyStructInstance() StructWithNoFields {
return empty_global_instance;
}
+
+const StructFoo = struct {
+ a: i32,
+ b: bool,
+ c: f32,
+};
+test "structs" {
+ var foo: StructFoo = undefined;
+ @memset(@ptrCast([*]u8, &foo), 0, @sizeOf(StructFoo));
+ foo.a += 1;
+ foo.b = foo.a == 1;
+ try testFoo(foo);
+ testMutation(&foo);
+ try expect(foo.c == 100);
+}
+fn testFoo(foo: StructFoo) !void {
+ try expect(foo.b);
+}
+fn testMutation(foo: *StructFoo) void {
+ foo.c = 100;
+}
diff --git a/test/behavior/struct_stage1.zig b/test/behavior/struct_stage1.zig
index 9f084ceb85..fd19b37661 100644
--- a/test/behavior/struct_stage1.zig
+++ b/test/behavior/struct_stage1.zig
@@ -30,26 +30,11 @@ const VoidStructFieldsFoo = struct {
c: void,
};
-test "structs" {
- var foo: StructFoo = undefined;
- @memset(@ptrCast([*]u8, &foo), 0, @sizeOf(StructFoo));
- foo.a += 1;
- foo.b = foo.a == 1;
- try testFoo(foo);
- testMutation(&foo);
- try expect(foo.c == 100);
-}
const StructFoo = struct {
a: i32,
b: bool,
c: f32,
};
-fn testFoo(foo: StructFoo) !void {
- try expect(foo.b);
-}
-fn testMutation(foo: *StructFoo) void {
- foo.c = 100;
-}
const Node = struct {
val: Val,
@@ -84,7 +69,7 @@ test "struct byval assign" {
try expect(foo2.a == 1234);
}
-fn structInitializer() void {
+test "struct initializer" {
const val = Val{ .x = 42 };
try expect(val.x == 42);
}
From 1f2f9f05c254374044d8c30cce6f299d7a18da72 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Sat, 25 Sep 2021 22:18:43 -0700
Subject: [PATCH 119/160] stage2: implement zirCoerceResultPtr
and remove Module.simplePtrType and Module.ptrType in favor of `Type.ptr`.
---
src/Module.zig | 76 --------
src/Sema.zig | 306 ++++++++++++++++----------------
src/type.zig | 61 +++++--
test/behavior/struct.zig | 38 ++++
test/behavior/struct_stage1.zig | 29 +--
5 files changed, 239 insertions(+), 271 deletions(-)
diff --git a/src/Module.zig b/src/Module.zig
index 278f8621d8..dbece09255 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -4460,82 +4460,6 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) Co
return error.AnalysisFail;
}
-pub fn simplePtrType(
- arena: *Allocator,
- elem_ty: Type,
- mutable: bool,
- size: std.builtin.TypeInfo.Pointer.Size,
- @"addrspace": std.builtin.AddressSpace,
-) Allocator.Error!Type {
- return ptrType(
- arena,
- elem_ty,
- null,
- 0,
- @"addrspace",
- 0,
- 0,
- mutable,
- false,
- false,
- size,
- );
-}
-
-pub fn ptrType(
- arena: *Allocator,
- elem_ty: Type,
- sentinel: ?Value,
- @"align": u32,
- @"addrspace": std.builtin.AddressSpace,
- bit_offset: u16,
- host_size: u16,
- mutable: bool,
- @"allowzero": bool,
- @"volatile": bool,
- size: std.builtin.TypeInfo.Pointer.Size,
-) Allocator.Error!Type {
- assert(host_size == 0 or bit_offset < host_size * 8);
-
- if (sentinel != null or @"align" != 0 or @"addrspace" != .generic or
- bit_offset != 0 or host_size != 0 or @"allowzero" or @"volatile")
- {
- return Type.Tag.pointer.create(arena, .{
- .pointee_type = elem_ty,
- .sentinel = sentinel,
- .@"align" = @"align",
- .@"addrspace" = @"addrspace",
- .bit_offset = bit_offset,
- .host_size = host_size,
- .@"allowzero" = @"allowzero",
- .mutable = mutable,
- .@"volatile" = @"volatile",
- .size = size,
- });
- }
-
- if (!mutable and size == .Slice and elem_ty.eql(Type.initTag(.u8))) {
- return Type.initTag(.const_slice_u8);
- }
-
- // TODO stage1 type inference bug
- const T = Type.Tag;
-
- const type_payload = try arena.create(Type.Payload.ElemType);
- type_payload.* = .{
- .base = .{
- .tag = switch (size) {
- .One => if (mutable) T.single_mut_pointer else T.single_const_pointer,
- .Many => if (mutable) T.many_mut_pointer else T.many_const_pointer,
- .C => if (mutable) T.c_mut_pointer else T.c_const_pointer,
- .Slice => if (mutable) T.mut_slice else T.const_slice,
- },
- },
- .data = elem_ty,
- };
- return Type.initPayload(&type_payload.base);
-}
-
pub fn optionalType(arena: *Allocator, child_type: Type) Allocator.Error!Type {
switch (child_type.tag()) {
.single_const_pointer => return Type.Tag.optional_single_const_pointer.create(
diff --git a/src/Sema.zig b/src/Sema.zig
index 1001b388a9..533252d682 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -979,10 +979,38 @@ fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) C
}
fn zirCoerceResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- _ = inst;
const tracy = trace(@src());
defer tracy.end();
- return sema.mod.fail(&block.base, sema.src, "TODO implement zirCoerceResultPtr", .{});
+
+ const src: LazySrcLoc = sema.src;
+ const bin_inst = sema.code.instructions.items(.data)[inst].bin;
+ const pointee_ty = try sema.resolveType(block, src, bin_inst.lhs);
+ const ptr = sema.resolveInst(bin_inst.rhs);
+
+ // Create a runtime bitcast instruction with exactly the type the pointer wants.
+ const ptr_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = pointee_ty,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
+ try sema.requireRuntimeBlock(block, src);
+ const bitcasted_ptr = try block.addTyOp(.bitcast, ptr_ty, ptr);
+
+ if (Air.refToIndex(ptr)) |ptr_inst| {
+ if (sema.air_instructions.items(.tag)[ptr_inst] == .constant) {
+ const air_datas = sema.air_instructions.items(.data);
+ const ptr_val = sema.air_values.items[air_datas[ptr_inst].ty_pl.payload];
+ if (ptr_val.castTag(.inferred_alloc)) |inferred_alloc| {
+ // Add the stored instruction to the set we will use to resolve peer types
+ // for the inferred allocation.
+ // This instruction will not make it to codegen; it is only to participate
+ // in the `stored_inst_list` of the `inferred_alloc`.
+ const operand = try block.addTyOp(.bitcast, pointee_ty, .void_value);
+ try inferred_alloc.data.stored_inst_list.append(sema.arena, operand);
+ }
+ }
+ }
+
+ return bitcasted_ptr;
}
pub fn analyzeStructDecl(
@@ -1427,13 +1455,10 @@ fn zirRetPtr(
return sema.analyzeComptimeAlloc(block, sema.fn_ret_ty);
}
- const ptr_type = try Module.simplePtrType(
- sema.arena,
- sema.fn_ret_ty,
- true,
- .One,
- target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
- );
+ const ptr_type = try Type.ptr(sema.arena, .{
+ .pointee_type = sema.fn_ret_ty,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
return block.addTy(.alloc, ptr_type);
}
@@ -1581,13 +1606,10 @@ fn zirAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError
const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node };
const var_decl_src = inst_data.src();
const var_type = try sema.resolveType(block, ty_src, inst_data.operand);
- const ptr_type = try Module.simplePtrType(
- sema.arena,
- var_type,
- true,
- .One,
- target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
- );
+ const ptr_type = try Type.ptr(sema.arena, .{
+ .pointee_type = var_type,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
try sema.requireRuntimeBlock(block, var_decl_src);
return block.addTy(.alloc, ptr_type);
}
@@ -1604,13 +1626,10 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
return sema.analyzeComptimeAlloc(block, var_type);
}
try sema.validateVarType(block, ty_src, var_type);
- const ptr_type = try Module.simplePtrType(
- sema.arena,
- var_type,
- true,
- .One,
- target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
- );
+ const ptr_type = try Type.ptr(sema.arena, .{
+ .pointee_type = var_type,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
try sema.requireRuntimeBlock(block, var_decl_src);
return block.addTy(.alloc, ptr_type);
}
@@ -1670,13 +1689,10 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
try sema.mod.declareDeclDependency(sema.owner_decl, decl);
const final_elem_ty = try decl.ty.copy(sema.arena);
- const final_ptr_ty = try Module.simplePtrType(
- sema.arena,
- final_elem_ty,
- true,
- .One,
- target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
- );
+ const final_ptr_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = final_elem_ty,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
const final_ptr_ty_inst = try sema.addType(final_ptr_ty);
sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst;
@@ -1698,13 +1714,10 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
try sema.validateVarType(block, ty_src, final_elem_ty);
}
// Change it to a normal alloc.
- const final_ptr_ty = try Module.simplePtrType(
- sema.arena,
- final_elem_ty,
- true,
- .One,
- target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
- );
+ const final_ptr_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = final_elem_ty,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
sema.air_instructions.set(ptr_inst, .{
.tag = .alloc,
.data = .{ .ty = final_ptr_ty },
@@ -1858,14 +1871,11 @@ fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
}
const ptr = sema.resolveInst(bin_inst.lhs);
const value = sema.resolveInst(bin_inst.rhs);
- const ptr_ty = try Module.simplePtrType(
- sema.arena,
- sema.typeOf(value),
- true,
- .One,
+ const ptr_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = sema.typeOf(value),
// TODO figure out which address space is appropriate here
- target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
- );
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
// TODO detect when this store should be done at compile-time. For example,
// if expressions should force it when the condition is compile-time known.
const src: LazySrcLoc = .unneeded;
@@ -1912,14 +1922,10 @@ fn zirStoreToInferredPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index)
// for the inferred allocation.
try inferred_alloc.data.stored_inst_list.append(sema.arena, operand);
// Create a runtime bitcast instruction with exactly the type the pointer wants.
- const ptr_ty = try Module.simplePtrType(
- sema.arena,
- operand_ty,
- true,
- .One,
- // TODO figure out which address space is appropriate here
- target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
- );
+ const ptr_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = operand_ty,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
const bitcasted_ptr = try block.addTyOp(.bitcast, ptr_ty, ptr);
return sema.storePtr(block, src, bitcasted_ptr, operand);
}
@@ -3845,13 +3851,11 @@ fn zirOptionalPayloadPtr(
}
const child_type = try opt_type.optionalChildAlloc(sema.arena);
- const child_pointer = try Module.simplePtrType(
- sema.arena,
- child_type,
- !optional_ptr_ty.isConstPtr(),
- .One,
- optional_ptr_ty.ptrAddressSpace(),
- );
+ const child_pointer = try Type.ptr(sema.arena, .{
+ .pointee_type = child_type,
+ .mutable = !optional_ptr_ty.isConstPtr(),
+ .@"addrspace" = optional_ptr_ty.ptrAddressSpace(),
+ });
if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| {
if (try pointer_val.pointerDeref(sema.arena)) |val| {
@@ -3966,13 +3970,11 @@ fn zirErrUnionPayloadPtr(
return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand_ty.elemType()});
const payload_ty = operand_ty.elemType().errorUnionPayload();
- const operand_pointer_ty = try Module.simplePtrType(
- sema.arena,
- payload_ty,
- !operand_ty.isConstPtr(),
- .One,
- operand_ty.ptrAddressSpace(),
- );
+ const operand_pointer_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = payload_ty,
+ .mutable = !operand_ty.isConstPtr(),
+ .@"addrspace" = operand_ty.ptrAddressSpace(),
+ });
if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
if (try pointer_val.pointerDeref(sema.arena)) |val| {
@@ -7306,19 +7308,14 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp
const inst_data = sema.code.instructions.items(.data)[inst].ptr_type_simple;
const elem_type = try sema.resolveType(block, .unneeded, inst_data.elem_type);
- const ty = try Module.ptrType(
- sema.arena,
- elem_type,
- null,
- 0,
- .generic,
- 0,
- 0,
- inst_data.is_mutable,
- inst_data.is_allowzero,
- inst_data.is_volatile,
- inst_data.size,
- );
+ const ty = try Type.ptr(sema.arena, .{
+ .pointee_type = elem_type,
+ .@"addrspace" = .generic,
+ .mutable = inst_data.is_mutable,
+ .@"allowzero" = inst_data.is_allowzero,
+ .@"volatile" = inst_data.is_volatile,
+ .size = inst_data.size,
+ });
return sema.addType(ty);
}
@@ -7367,19 +7364,18 @@ fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
const elem_type = try sema.resolveType(block, .unneeded, extra.data.elem_type);
- const ty = try Module.ptrType(
- sema.arena,
- elem_type,
- sentinel,
- abi_align,
- address_space,
- bit_start,
- bit_end,
- inst_data.flags.is_mutable,
- inst_data.flags.is_allowzero,
- inst_data.flags.is_volatile,
- inst_data.size,
- );
+ const ty = try Type.ptr(sema.arena, .{
+ .pointee_type = elem_type,
+ .sentinel = sentinel,
+ .@"align" = abi_align,
+ .@"addrspace" = address_space,
+ .bit_offset = bit_start,
+ .host_size = bit_end,
+ .mutable = inst_data.flags.is_mutable,
+ .@"allowzero" = inst_data.flags.is_allowzero,
+ .@"volatile" = inst_data.flags.is_volatile,
+ .size = inst_data.size,
+ });
return sema.addType(ty);
}
@@ -8456,19 +8452,15 @@ fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro
});
}
const src_ptr_info = uncasted_src_ptr_ty.ptrInfo().data;
- const wanted_src_ptr_ty = try Module.ptrType(
- sema.arena,
- dest_ptr_ty.elemType2(),
- null,
- src_ptr_info.@"align",
- src_ptr_info.@"addrspace",
- 0,
- 0,
- false,
- src_ptr_info.@"allowzero",
- src_ptr_info.@"volatile",
- .Many,
- );
+ const wanted_src_ptr_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = dest_ptr_ty.elemType2(),
+ .@"align" = src_ptr_info.@"align",
+ .@"addrspace" = src_ptr_info.@"addrspace",
+ .mutable = false,
+ .@"allowzero" = src_ptr_info.@"allowzero",
+ .@"volatile" = src_ptr_info.@"volatile",
+ .size = .Many,
+ });
const src_ptr = try sema.coerce(block, wanted_src_ptr_ty, uncasted_src_ptr, src_src);
const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src);
@@ -8922,13 +8914,10 @@ fn panicWithMsg(
const panic_fn = try sema.getBuiltin(block, src, "panic");
const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace");
const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty);
- const ptr_stack_trace_ty = try Module.simplePtrType(
- arena,
- stack_trace_ty,
- true,
- .One,
- target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic
- );
+ const ptr_stack_trace_ty = try Type.ptr(arena, .{
+ .pointee_type = stack_trace_ty,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic
+ });
const null_stack_trace = try sema.addConstant(
try Module.optionalType(arena, ptr_stack_trace_ty),
Value.initTag(.null_value),
@@ -9407,13 +9396,11 @@ fn structFieldPtr(
const field_index = struct_obj.fields.getIndex(field_name) orelse
return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name);
const field = struct_obj.fields.values()[field_index];
- const ptr_field_ty = try Module.simplePtrType(
- arena,
- field.ty,
- struct_ptr_ty.ptrIsMutable(),
- .One,
- struct_ptr_ty.ptrAddressSpace(),
- );
+ const ptr_field_ty = try Type.ptr(arena, .{
+ .pointee_type = field.ty,
+ .mutable = struct_ptr_ty.ptrIsMutable(),
+ .@"addrspace" = struct_ptr_ty.ptrAddressSpace(),
+ });
if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| {
return sema.addConstant(
@@ -9512,13 +9499,11 @@ fn unionFieldPtr(
return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name);
const field = union_obj.fields.values()[field_index];
- const ptr_field_ty = try Module.simplePtrType(
- arena,
- field.ty,
- union_ptr_ty.ptrIsMutable(),
- .One,
- union_ptr_ty.ptrAddressSpace(),
- );
+ const ptr_field_ty = try Type.ptr(arena, .{
+ .pointee_type = field.ty,
+ .mutable = union_ptr_ty.ptrIsMutable(),
+ .@"addrspace" = union_ptr_ty.ptrAddressSpace(),
+ });
if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| {
// TODO detect inactive union field and emit compile error
@@ -9694,13 +9679,11 @@ fn elemPtrArray(
) CompileError!Air.Inst.Ref {
const array_ptr_ty = sema.typeOf(array_ptr);
const pointee_type = array_ptr_ty.elemType().elemType();
- const result_ty = try Module.simplePtrType(
- sema.arena,
- pointee_type,
- array_ptr_ty.ptrIsMutable(),
- .One,
- array_ptr_ty.ptrAddressSpace(),
- );
+ const result_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = pointee_type,
+ .mutable = array_ptr_ty.ptrIsMutable(),
+ .@"addrspace" = array_ptr_ty.ptrAddressSpace(),
+ });
if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| {
if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| {
@@ -10243,11 +10226,19 @@ fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref {
const decl_tv = try decl.typedValue();
if (decl_tv.val.castTag(.variable)) |payload| {
const variable = payload.data;
- const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One, decl.@"addrspace");
+ const ty = try Type.ptr(sema.arena, .{
+ .pointee_type = decl_tv.ty,
+ .mutable = variable.is_mutable,
+ .@"addrspace" = decl.@"addrspace",
+ });
return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl));
}
return sema.addConstant(
- try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One, decl.@"addrspace"),
+ try Type.ptr(sema.arena, .{
+ .pointee_type = decl_tv.ty,
+ .mutable = false,
+ .@"addrspace" = decl.@"addrspace",
+ }),
try Value.Tag.decl_ref.create(sema.arena, decl),
);
}
@@ -10271,8 +10262,15 @@ fn analyzeRef(
try sema.requireRuntimeBlock(block, src);
const address_space = target_util.defaultAddressSpace(sema.mod.getTarget(), .local);
- const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One, address_space);
- const mut_ptr_type = try Module.simplePtrType(sema.arena, operand_ty, true, .One, address_space);
+ const ptr_type = try Type.ptr(sema.arena, .{
+ .pointee_type = operand_ty,
+ .mutable = false,
+ .@"addrspace" = address_space,
+ });
+ const mut_ptr_type = try Type.ptr(sema.arena, .{
+ .pointee_type = operand_ty,
+ .@"addrspace" = address_space,
+ });
const alloc = try block.addTy(.alloc, mut_ptr_type);
try sema.storePtr(block, src, alloc, operand);
@@ -10428,19 +10426,16 @@ fn analyzeSlice(
}
}
}
- const return_type = try Module.ptrType(
- sema.arena,
- return_elem_type,
- if (end_opt == .none) slice_sentinel else null,
- 0, // TODO alignment
- if (ptr_child.zigTypeTag() == .Pointer) ptr_child.ptrAddressSpace() else .generic,
- 0,
- 0,
- !ptr_child.isConstPtr(),
- ptr_child.isAllowzeroPtr(),
- ptr_child.isVolatilePtr(),
- return_ptr_size,
- );
+ const return_type = try Type.ptr(sema.arena, .{
+ .pointee_type = return_elem_type,
+ .sentinel = if (end_opt == .none) slice_sentinel else null,
+ .@"align" = 0, // TODO alignment
+ .@"addrspace" = if (ptr_child.zigTypeTag() == .Pointer) ptr_child.ptrAddressSpace() else .generic,
+ .mutable = !ptr_child.isConstPtr(),
+ .@"allowzero" = ptr_child.isAllowzeroPtr(),
+ .@"volatile" = ptr_child.isVolatilePtr(),
+ .size = return_ptr_size,
+ });
_ = return_type;
return sema.mod.fail(&block.base, src, "TODO implement analysis of slice", .{});
@@ -11626,13 +11621,10 @@ fn analyzeComptimeAlloc(
block: *Scope.Block,
var_type: Type,
) CompileError!Air.Inst.Ref {
- const ptr_type = try Module.simplePtrType(
- sema.arena,
- var_type,
- true,
- .One,
- target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant),
- );
+ const ptr_type = try Type.ptr(sema.arena, .{
+ .pointee_type = var_type,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant),
+ });
var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit();
diff --git a/src/type.zig b/src/type.zig
index bd008e809a..48c65c1008 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -3652,12 +3652,12 @@ pub const Type = extern union {
}
pub fn create(comptime t: Tag, ally: *Allocator, data: Data(t)) error{OutOfMemory}!file_struct.Type {
- const ptr = try ally.create(t.Type());
- ptr.* = .{
+ const p = try ally.create(t.Type());
+ p.* = .{
.base = .{ .tag = t },
.data = data,
};
- return file_struct.Type{ .ptr_otherwise = &ptr.base };
+ return file_struct.Type{ .ptr_otherwise = &p.base };
}
pub fn Data(comptime t: Tag) type {
@@ -3747,19 +3747,23 @@ pub const Type = extern union {
pub const base_tag = Tag.pointer;
base: Payload = Payload{ .tag = base_tag },
- data: struct {
+ data: Data,
+
+ pub const Data = struct {
pointee_type: Type,
- sentinel: ?Value,
+ sentinel: ?Value = null,
/// If zero use pointee_type.AbiAlign()
- @"align": u32,
+ @"align": u32 = 0,
+ /// See src/target.zig defaultAddressSpace function for how to obtain
+ /// an appropriate value for this field.
@"addrspace": std.builtin.AddressSpace,
- bit_offset: u16,
- host_size: u16,
- @"allowzero": bool,
- mutable: bool,
- @"volatile": bool,
- size: std.builtin.TypeInfo.Pointer.Size,
- },
+ bit_offset: u16 = 0,
+ host_size: u16 = 0,
+ @"allowzero": bool = false,
+ mutable: bool = true, // TODO change this to const, not mutable
+ @"volatile": bool = false,
+ size: std.builtin.TypeInfo.Pointer.Size = .One,
+ };
};
pub const ErrorUnion = struct {
@@ -3815,6 +3819,37 @@ pub const Type = extern union {
data: *Module.EnumSimple,
};
};
+
+ pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {
+ assert(d.host_size == 0 or d.bit_offset < d.host_size * 8);
+
+ if (d.sentinel != null or d.@"align" != 0 or d.@"addrspace" != .generic or
+ d.bit_offset != 0 or d.host_size != 0 or d.@"allowzero" or d.@"volatile")
+ {
+ return Type.Tag.pointer.create(arena, d);
+ }
+
+ if (!d.mutable and d.size == .Slice and d.pointee_type.eql(Type.initTag(.u8))) {
+ return Type.initTag(.const_slice_u8);
+ }
+
+ // TODO stage1 type inference bug
+ const T = Type.Tag;
+
+ const type_payload = try arena.create(Type.Payload.ElemType);
+ type_payload.* = .{
+ .base = .{
+ .tag = switch (d.size) {
+ .One => if (d.mutable) T.single_mut_pointer else T.single_const_pointer,
+ .Many => if (d.mutable) T.many_mut_pointer else T.many_const_pointer,
+ .C => if (d.mutable) T.c_mut_pointer else T.c_const_pointer,
+ .Slice => if (d.mutable) T.mut_slice else T.const_slice,
+ },
+ },
+ .data = d.pointee_type,
+ };
+ return Type.initPayload(&type_payload.base);
+ }
};
pub const CType = enum {
diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig
index e048848799..2dde3c930d 100644
--- a/test/behavior/struct.zig
+++ b/test/behavior/struct.zig
@@ -52,3 +52,41 @@ fn testFoo(foo: StructFoo) !void {
fn testMutation(foo: *StructFoo) void {
foo.c = 100;
}
+
+test "struct byval assign" {
+ var foo1: StructFoo = undefined;
+ var foo2: StructFoo = undefined;
+
+ foo1.a = 1234;
+ foo2.a = 0;
+ try expect(foo2.a == 0);
+ foo2 = foo1;
+ try expect(foo2.a == 1234);
+}
+
+const Node = struct {
+ val: Val,
+ next: *Node,
+};
+
+const Val = struct {
+ x: i32,
+};
+
+test "struct initializer" {
+ const val = Val{ .x = 42 };
+ try expect(val.x == 42);
+}
+
+const MemberFnTestFoo = struct {
+ x: i32,
+ fn member(foo: MemberFnTestFoo) i32 {
+ return foo.x;
+ }
+};
+
+test "call member function directly" {
+ const instance = MemberFnTestFoo{ .x = 1234 };
+ const result = MemberFnTestFoo.member(instance);
+ try expect(result == 1234);
+}
diff --git a/test/behavior/struct_stage1.zig b/test/behavior/struct_stage1.zig
index fd19b37661..b5394afd50 100644
--- a/test/behavior/struct_stage1.zig
+++ b/test/behavior/struct_stage1.zig
@@ -5,6 +5,7 @@ const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualSlices = std.testing.expectEqualSlices;
const maxInt = std.math.maxInt;
+
top_level_field: i32,
test "top level fields" {
@@ -58,22 +59,6 @@ test "struct point to self" {
try expect(node.next.next.next.val.x == 1);
}
-test "struct byval assign" {
- var foo1: StructFoo = undefined;
- var foo2: StructFoo = undefined;
-
- foo1.a = 1234;
- foo2.a = 0;
- try expect(foo2.a == 0);
- foo2 = foo1;
- try expect(foo2.a == 1234);
-}
-
-test "struct initializer" {
- const val = Val{ .x = 42 };
- try expect(val.x == 42);
-}
-
test "fn call of struct field" {
const Foo = struct {
ptr: fn () i32,
@@ -91,22 +76,16 @@ test "fn call of struct field" {
try expect(S.callStructField(Foo{ .ptr = S.aFunc }) == 13);
}
-test "store member function in variable" {
- const instance = MemberFnTestFoo{ .x = 1234 };
- const memberFn = MemberFnTestFoo.member;
- const result = memberFn(instance);
- try expect(result == 1234);
-}
const MemberFnTestFoo = struct {
x: i32,
fn member(foo: MemberFnTestFoo) i32 {
return foo.x;
}
};
-
-test "call member function directly" {
+test "store member function in variable" {
const instance = MemberFnTestFoo{ .x = 1234 };
- const result = MemberFnTestFoo.member(instance);
+ const memberFn = MemberFnTestFoo.member;
+ const result = memberFn(instance);
try expect(result == 1234);
}
From 25266d08046df6032007b46346faf01a2f40ef31 Mon Sep 17 00:00:00 2001
From: "Mr. Paul"
Date: Mon, 27 Sep 2021 09:34:59 +0700
Subject: [PATCH 120/160] Langref: fix HTML escaped symbol WASM JavaScript code
example
docgen HTML escapes characters inside of `syntax_block`s. This commit replaces the escaped
greater than with the `>` character. No other occurrences were found.
Fixes #9840
---
doc/langref.html.in | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 561065bc0d..a5dfa5c927 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -10543,8 +10543,8 @@ const typedArray = new Uint8Array(source);
WebAssembly.instantiate(typedArray, {
env: {
- print: (result) => { console.log(`The result is ${result}`); }
- }}).then(result => {
+ print: (result) => { console.log(`The result is ${result}`); }
+ }}).then(result => {
const add = result.instance.exports.add;
add(1, 2);
});{#end_syntax_block#}
From c0aa4a1a42b3e0d312bd274799be67d60a1c0238 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Mon, 27 Sep 2021 19:48:42 -0700
Subject: [PATCH 121/160] stage2: implement basic unions
* AIR instructions struct_field_ptr and related functions now are also
emitted by the frontend for unions. Backends must inspect the type
of the pointer operand to lower the instructions correctly.
- These will be renamed to `agg_field_ptr` (short for "aggregate") in
the future.
* Introduce the new `set_union_tag` AIR instruction.
* Introduce `Module.EnumNumbered` and associated `Type` methods. This
is for enums which have no decls, but do have the possibility of
overriding the integer tag type and tag values.
* Sema: Implement support for union tag types in both the
auto-generated and explicitly-provided cases, as well as explicitly
provided enum tag values in union declarations.
* LLVM backend: implement lowering union types, union field pointer
instructions, and the new `set_union_tag` instruction.
---
src/Air.zig | 14 +-
src/Liveness.zig | 1 +
src/Module.zig | 107 ++++++++++-
src/Sema.zig | 320 +++++++++++++++++++++++++++------
src/codegen.zig | 9 +
src/codegen/c.zig | 16 ++
src/codegen/llvm.zig | 80 ++++++++-
src/print_air.zig | 1 +
src/type.zig | 170 +++++++++---------
test/behavior/union.zig | 12 ++
test/behavior/union_stage1.zig | 7 -
11 files changed, 576 insertions(+), 161 deletions(-)
diff --git a/src/Air.zig b/src/Air.zig
index 4341271f3a..40070dccfb 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -270,19 +270,26 @@ pub const Inst = struct {
/// wrap from E to E!T
/// Uses the `ty_op` field.
wrap_errunion_err,
- /// Given a pointer to a struct and a field index, returns a pointer to the field.
+ /// Given a pointer to a struct or union and a field index, returns a pointer to the field.
/// Uses the `ty_pl` field, payload is `StructField`.
+ /// TODO rename to `agg_field_ptr`.
struct_field_ptr,
- /// Given a pointer to a struct, returns a pointer to the field.
+ /// Given a pointer to a struct or union, returns a pointer to the field.
/// The field index is the number at the end of the name.
/// Uses `ty_op` field.
+ /// TODO rename to `agg_field_ptr_index_X`
struct_field_ptr_index_0,
struct_field_ptr_index_1,
struct_field_ptr_index_2,
struct_field_ptr_index_3,
- /// Given a byval struct and a field index, returns the field byval.
+ /// Given a byval struct or union and a field index, returns the field byval.
/// Uses the `ty_pl` field, payload is `StructField`.
+ /// TODO rename to `agg_field_val`
struct_field_val,
+ /// Given a pointer to a tagged union, set its tag to the provided value.
+ /// Result type is always void.
+ /// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value.
+ set_union_tag,
/// Given a slice value, return the length.
/// Result type is always usize.
/// Uses the `ty_op` field.
@@ -643,6 +650,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.atomic_store_seq_cst,
.memset,
.memcpy,
+ .set_union_tag,
=> return Type.initTag(.void),
.ptrtoint,
diff --git a/src/Liveness.zig b/src/Liveness.zig
index 42ab1ab351..9a7126d135 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -256,6 +256,7 @@ fn analyzeInst(
.atomic_store_monotonic,
.atomic_store_release,
.atomic_store_seq_cst,
+ .set_union_tag,
=> {
const o = inst_datas[inst].bin_op;
return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
diff --git a/src/Module.zig b/src/Module.zig
index dbece09255..83bbbb6366 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -859,6 +859,36 @@ pub const EnumSimple = struct {
}
};
+/// Represents the data that an enum declaration provides, when there are no
+/// declarations. However an integer tag type is provided, and the enum tag values
+/// are explicitly provided.
+pub const EnumNumbered = struct {
+ /// The Decl that corresponds to the enum itself.
+ owner_decl: *Decl,
+ /// An integer type which is used for the numerical value of the enum.
+ /// Whether zig chooses this type or the user specifies it, it is stored here.
+ tag_ty: Type,
+ /// Set of field names in declaration order.
+ fields: NameMap,
+ /// Maps integer tag value to field index.
+ /// Entries are in declaration order, same as `fields`.
+ /// If this hash map is empty, it means the enum tags are auto-numbered.
+ values: ValueMap,
+ /// Offset from `owner_decl`, points to the enum decl AST node.
+ node_offset: i32,
+
+ pub const NameMap = EnumFull.NameMap;
+ pub const ValueMap = EnumFull.ValueMap;
+
+ pub fn srcLoc(self: EnumNumbered) SrcLoc {
+ return .{
+ .file_scope = self.owner_decl.getFileScope(),
+ .parent_decl_node = self.owner_decl.src_node,
+ .lazy = .{ .node_offset = self.node_offset },
+ };
+ }
+};
+
/// Represents the data that an enum declaration provides, when there is
/// at least one tag value explicitly specified, or at least one declaration.
pub const EnumFull = struct {
@@ -868,16 +898,17 @@ pub const EnumFull = struct {
/// Whether zig chooses this type or the user specifies it, it is stored here.
tag_ty: Type,
/// Set of field names in declaration order.
- fields: std.StringArrayHashMapUnmanaged(void),
+ fields: NameMap,
/// Maps integer tag value to field index.
/// Entries are in declaration order, same as `fields`.
/// If this hash map is empty, it means the enum tags are auto-numbered.
values: ValueMap,
- /// Represents the declarations inside this struct.
+ /// Represents the declarations inside this enum.
namespace: Scope.Namespace,
/// Offset from `owner_decl`, points to the enum decl AST node.
node_offset: i32,
+ pub const NameMap = std.StringArrayHashMapUnmanaged(void);
pub const ValueMap = std.ArrayHashMapUnmanaged(Value, void, Value.ArrayHashContext, false);
pub fn srcLoc(self: EnumFull) SrcLoc {
@@ -933,6 +964,44 @@ pub const Union = struct {
.lazy = .{ .node_offset = self.node_offset },
};
}
+
+ pub fn haveFieldTypes(u: Union) bool {
+ return switch (u.status) {
+ .none,
+ .field_types_wip,
+ => false,
+ .have_field_types,
+ .layout_wip,
+ .have_layout,
+ => true,
+ };
+ }
+
+ pub fn onlyTagHasCodegenBits(u: Union) bool {
+ assert(u.haveFieldTypes());
+ for (u.fields.values()) |field| {
+ if (field.ty.hasCodeGenBits()) return false;
+ }
+ return true;
+ }
+
+ pub fn mostAlignedField(u: Union, target: Target) u32 {
+ assert(u.haveFieldTypes());
+ var most_alignment: u64 = 0;
+ var most_index: usize = undefined;
+ for (u.fields.values()) |field, i| {
+ if (!field.ty.hasCodeGenBits()) continue;
+ const field_align = if (field.abi_align.tag() == .abi_align_default)
+ field.ty.abiAlignment(target)
+ else
+ field.abi_align.toUnsignedInt();
+ if (field_align > most_alignment) {
+ most_alignment = field_align;
+ most_index = i;
+ }
+ }
+ return @intCast(u32, most_index);
+ }
};
/// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
@@ -1543,6 +1612,40 @@ pub const Scope = struct {
});
}
+ pub fn addStructFieldPtr(
+ block: *Block,
+ struct_ptr: Air.Inst.Ref,
+ field_index: u32,
+ ptr_field_ty: Type,
+ ) !Air.Inst.Ref {
+ const ty = try block.sema.addType(ptr_field_ty);
+ const tag: Air.Inst.Tag = switch (field_index) {
+ 0 => .struct_field_ptr_index_0,
+ 1 => .struct_field_ptr_index_1,
+ 2 => .struct_field_ptr_index_2,
+ 3 => .struct_field_ptr_index_3,
+ else => {
+ return block.addInst(.{
+ .tag = .struct_field_ptr,
+ .data = .{ .ty_pl = .{
+ .ty = ty,
+ .payload = try block.sema.addExtra(Air.StructField{
+ .struct_operand = struct_ptr,
+ .field_index = @intCast(u32, field_index),
+ }),
+ } },
+ });
+ },
+ };
+ return block.addInst(.{
+ .tag = tag,
+ .data = .{ .ty_op = .{
+ .ty = ty,
+ .operand = struct_ptr,
+ } },
+ });
+ }
+
pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
return Air.indexToRef(try block.addInstAsIndex(inst));
}
diff --git a/src/Sema.zig b/src/Sema.zig
index 533252d682..f076389797 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -1625,7 +1625,7 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
if (block.is_comptime) {
return sema.analyzeComptimeAlloc(block, var_type);
}
- try sema.validateVarType(block, ty_src, var_type);
+ try sema.validateVarType(block, ty_src, var_type, false);
const ptr_type = try Type.ptr(sema.arena, .{
.pointee_type = var_type,
.@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
@@ -1711,7 +1711,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde
const peer_inst_list = inferred_alloc.data.stored_inst_list.items;
const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_inst_list, .none);
if (var_is_mut) {
- try sema.validateVarType(block, ty_src, final_elem_ty);
+ try sema.validateVarType(block, ty_src, final_elem_ty, false);
}
// Change it to a normal alloc.
const final_ptr_ty = try Type.ptr(sema.arena, .{
@@ -1730,19 +1730,82 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind
const tracy = trace(@src());
defer tracy.end();
- const gpa = sema.gpa;
- const mod = sema.mod;
const validate_inst = sema.code.instructions.items(.data)[inst].pl_node;
- const struct_init_src = validate_inst.src();
+ const init_src = validate_inst.src();
const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);
const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len];
+ const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
+ const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
+ const object_ptr = sema.resolveInst(field_ptr_extra.lhs);
+ const agg_ty = sema.typeOf(object_ptr).elemType();
+ switch (agg_ty.zigTypeTag()) {
+ .Struct => return sema.validateStructInitPtr(
+ block,
+ agg_ty.castTag(.@"struct").?.data,
+ init_src,
+ instrs,
+ ),
+ .Union => return sema.validateUnionInitPtr(
+ block,
+ agg_ty.cast(Type.Payload.Union).?.data,
+ init_src,
+ instrs,
+ object_ptr,
+ ),
+ else => unreachable,
+ }
+}
- const struct_obj: *Module.Struct = s: {
- const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node;
- const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
- const object_ptr = sema.resolveInst(field_ptr_extra.lhs);
- break :s sema.typeOf(object_ptr).elemType().castTag(.@"struct").?.data;
- };
+fn validateUnionInitPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ union_obj: *Module.Union,
+ init_src: LazySrcLoc,
+ instrs: []const Zir.Inst.Index,
+ union_ptr: Air.Inst.Ref,
+) CompileError!void {
+ const mod = sema.mod;
+
+ if (instrs.len != 1) {
+ // TODO add note for other field
+ // TODO add note for union declared here
+ return mod.fail(&block.base, init_src, "only one union field can be active at once", .{});
+ }
+
+ const field_ptr = instrs[0];
+ const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
+ const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node };
+ const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
+ const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start);
+ const field_index_big = union_obj.fields.getIndex(field_name) orelse
+ return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);
+ const field_index = @intCast(u32, field_index_big);
+
+ // TODO here we need to go back and see if we need to convert the union
+ // to a comptime-known value. This will involve editing the AIR code we have
+ // generated so far - in particular deleting some runtime pointer bitcast
+ // instructions which are not actually needed if the initialization expression
+ // ends up being comptime-known.
+
+ // Otherwise, we set the new union tag now.
+ const new_tag = try sema.addConstant(
+ union_obj.tag_ty,
+ try Value.Tag.enum_field_index.create(sema.arena, field_index),
+ );
+
+ try sema.requireRuntimeBlock(block, init_src);
+ _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag);
+}
+
+fn validateStructInitPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ struct_obj: *Module.Struct,
+ init_src: LazySrcLoc,
+ instrs: []const Zir.Inst.Index,
+) CompileError!void {
+ const gpa = sema.gpa;
+ const mod = sema.mod;
// Maps field index to field_ptr index of where it was already initialized.
const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count());
@@ -1781,9 +1844,9 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind
const template = "missing struct field: {s}";
const args = .{field_name};
if (root_msg) |msg| {
- try mod.errNote(&block.base, struct_init_src, msg, template, args);
+ try mod.errNote(&block.base, init_src, msg, template, args);
} else {
- root_msg = try mod.errMsg(&block.base, struct_init_src, template, args);
+ root_msg = try mod.errMsg(&block.base, init_src, template, args);
}
}
if (root_msg) |msg| {
@@ -8037,7 +8100,7 @@ fn checkAtomicOperandType(
const max_atomic_bits = target_util.largestAtomicBits(target);
const int_ty = switch (ty.zigTypeTag()) {
.Int => ty,
- .Enum => ty.enumTagType(&buffer),
+ .Enum => ty.intTagType(&buffer),
.Float => {
const bit_count = ty.floatBits(target);
if (bit_count > max_atomic_bits) {
@@ -8621,11 +8684,7 @@ fn zirVarExtended(
return sema.failWithNeededComptime(block, init_src);
} else Value.initTag(.unreachable_value);
- if (!var_ty.isValidVarType(small.is_extern)) {
- return sema.mod.fail(&block.base, mut_src, "variable of type '{}' must be const", .{
- var_ty,
- });
- }
+ try sema.validateVarType(block, mut_src, var_ty, small.is_extern);
if (lib_name != null) {
// Look at the sema code for functions which has this logic, it just needs to
@@ -8810,9 +8869,54 @@ fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Typ
}
}
-fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void {
- if (!ty.isValidVarType(false)) {
- return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty});
+/// Emit a compile error if type cannot be used for a runtime variable.
+fn validateVarType(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ var_ty: Type,
+ is_extern: bool,
+) CompileError!void {
+ var ty = var_ty;
+ const ok: bool = while (true) switch (ty.zigTypeTag()) {
+ .Bool,
+ .Int,
+ .Float,
+ .ErrorSet,
+ .Enum,
+ .Frame,
+ .AnyFrame,
+ => break true,
+
+ .BoundFn,
+ .ComptimeFloat,
+ .ComptimeInt,
+ .EnumLiteral,
+ .NoReturn,
+ .Type,
+ .Void,
+ .Undefined,
+ .Null,
+ => break false,
+
+ .Opaque => break is_extern,
+
+ .Optional => {
+ var buf: Type.Payload.ElemType = undefined;
+ const child_ty = ty.optionalChild(&buf);
+ return validateVarType(sema, block, src, child_ty, is_extern);
+ },
+ .Pointer, .Array, .Vector => ty = ty.elemType(),
+ .ErrorUnion => ty = ty.errorUnionPayload(),
+
+ .Fn => @panic("TODO fn validateVarType"),
+ .Struct, .Union => {
+ const resolved_ty = try sema.resolveTypeFields(block, src, ty);
+ break !resolved_ty.requiresComptime();
+ },
+ } else unreachable; // TODO should not need else unreachable
+ if (!ok) {
+ return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{var_ty});
}
}
@@ -9393,8 +9497,9 @@ fn structFieldPtr(
const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty);
const struct_obj = struct_ty.castTag(.@"struct").?.data;
- const field_index = struct_obj.fields.getIndex(field_name) orelse
+ const field_index_big = struct_obj.fields.getIndex(field_name) orelse
return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name);
+ const field_index = @intCast(u32, field_index_big);
const field = struct_obj.fields.values()[field_index];
const ptr_field_ty = try Type.ptr(arena, .{
.pointee_type = field.ty,
@@ -9413,31 +9518,7 @@ fn structFieldPtr(
}
try sema.requireRuntimeBlock(block, src);
- const tag: Air.Inst.Tag = switch (field_index) {
- 0 => .struct_field_ptr_index_0,
- 1 => .struct_field_ptr_index_1,
- 2 => .struct_field_ptr_index_2,
- 3 => .struct_field_ptr_index_3,
- else => {
- return block.addInst(.{
- .tag = .struct_field_ptr,
- .data = .{ .ty_pl = .{
- .ty = try sema.addType(ptr_field_ty),
- .payload = try sema.addExtra(Air.StructField{
- .struct_operand = struct_ptr,
- .field_index = @intCast(u32, field_index),
- }),
- } },
- });
- },
- };
- return block.addInst(.{
- .tag = tag,
- .data = .{ .ty_op = .{
- .ty = try sema.addType(ptr_field_ty),
- .operand = struct_ptr,
- } },
- });
+ return block.addStructFieldPtr(struct_ptr, field_index, ptr_field_ty);
}
fn structFieldVal(
@@ -9487,7 +9568,6 @@ fn unionFieldPtr(
field_name_src: LazySrcLoc,
unresolved_union_ty: Type,
) CompileError!Air.Inst.Ref {
- const mod = sema.mod;
const arena = sema.arena;
assert(unresolved_union_ty.zigTypeTag() == .Union);
@@ -9495,8 +9575,9 @@ fn unionFieldPtr(
const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty);
const union_obj = union_ty.cast(Type.Payload.Union).?.data;
- const field_index = union_obj.fields.getIndex(field_name) orelse
+ const field_index_big = union_obj.fields.getIndex(field_name) orelse
return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name);
+ const field_index = @intCast(u32, field_index_big);
const field = union_obj.fields.values()[field_index];
const ptr_field_ty = try Type.ptr(arena, .{
@@ -9517,7 +9598,7 @@ fn unionFieldPtr(
}
try sema.requireRuntimeBlock(block, src);
- return mod.fail(&block.base, src, "TODO implement runtime union field access", .{});
+ return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty);
}
fn unionFieldVal(
@@ -11160,6 +11241,28 @@ fn analyzeUnionFields(
if (body.len != 0) {
_ = try sema.analyzeBody(block, body);
}
+ var int_tag_ty: Type = undefined;
+ var enum_field_names: ?*Module.EnumNumbered.NameMap = null;
+ var enum_value_map: ?*Module.EnumNumbered.ValueMap = null;
+ if (tag_type_ref != .none) {
+ const provided_ty = try sema.resolveType(block, src, tag_type_ref);
+ if (small.auto_enum_tag) {
+ // The provided type is an integer type and we must construct the enum tag type here.
+ int_tag_ty = provided_ty;
+ union_obj.tag_ty = try sema.generateUnionTagTypeNumbered(block, fields_len, provided_ty);
+ enum_field_names = &union_obj.tag_ty.castTag(.enum_numbered).?.data.fields;
+ enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values;
+ } else {
+ // The provided type is the enum tag type.
+ union_obj.tag_ty = provided_ty;
+ }
+ } else {
+ // If auto_enum_tag is false, this is an untagged union. However, for semantic analysis
+ // purposes, we still auto-generate an enum tag type the same way. That the union is
+ // untagged is represented by the Type tag (union vs union_tagged).
+ union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, fields_len);
+ enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields;
+ }
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
@@ -11198,12 +11301,25 @@ fn analyzeUnionFields(
break :blk align_ref;
} else .none;
- if (has_tag) {
+ const tag_ref: Zir.Inst.Ref = if (has_tag) blk: {
+ const tag_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
+ break :blk tag_ref;
+ } else .none;
+
+ if (enum_value_map) |map| {
+ const tag_src = src; // TODO better source location
+ const coerced = try sema.coerce(block, int_tag_ty, tag_ref, tag_src);
+ const val = try sema.resolveConstValue(block, tag_src, coerced);
+ map.putAssumeCapacityContext(val, {}, .{ .ty = int_tag_ty });
}
// This string needs to outlive the ZIR code.
const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
+ if (enum_field_names) |set| {
+ set.putAssumeCapacity(field_name, {});
+ }
+
const field_ty: Type = if (field_type_ref == .none)
Type.initTag(.void)
else
@@ -11225,11 +11341,84 @@ fn analyzeUnionFields(
// But only resolve the source location if we need to emit a compile error.
const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val;
gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator);
+ } else {
+ gop.value_ptr.abi_align = Value.initTag(.abi_align_default);
}
}
+}
- // TODO resolve the union tag_type_ref
- _ = tag_type_ref;
+fn generateUnionTagTypeNumbered(
+ sema: *Sema,
+ block: *Scope.Block,
+ fields_len: u32,
+ int_ty: Type,
+) !Type {
+ const mod = sema.mod;
+
+ var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+ errdefer new_decl_arena.deinit();
+
+ const enum_obj = try new_decl_arena.allocator.create(Module.EnumNumbered);
+ const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumNumbered);
+ enum_ty_payload.* = .{
+ .base = .{ .tag = .enum_numbered },
+ .data = enum_obj,
+ };
+ const enum_ty = Type.initPayload(&enum_ty_payload.base);
+ const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty);
+ // TODO better type name
+ const new_decl = try mod.createAnonymousDecl(&block.base, .{
+ .ty = Type.initTag(.type),
+ .val = enum_val,
+ });
+ new_decl.owns_tv = true;
+ errdefer sema.mod.deleteAnonDecl(&block.base, new_decl);
+
+ enum_obj.* = .{
+ .owner_decl = new_decl,
+ .tag_ty = int_ty,
+ .fields = .{},
+ .values = .{},
+ .node_offset = 0,
+ };
+ // Here we pre-allocate the maps using the decl arena.
+ try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len);
+ try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{ .ty = int_ty });
+ try new_decl.finalizeNewArena(&new_decl_arena);
+ return enum_ty;
+}
+
+fn generateUnionTagTypeSimple(sema: *Sema, block: *Scope.Block, fields_len: u32) !Type {
+ const mod = sema.mod;
+
+ var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+ errdefer new_decl_arena.deinit();
+
+ const enum_obj = try new_decl_arena.allocator.create(Module.EnumSimple);
+ const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumSimple);
+ enum_ty_payload.* = .{
+ .base = .{ .tag = .enum_simple },
+ .data = enum_obj,
+ };
+ const enum_ty = Type.initPayload(&enum_ty_payload.base);
+ const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty);
+ // TODO better type name
+ const new_decl = try mod.createAnonymousDecl(&block.base, .{
+ .ty = Type.initTag(.type),
+ .val = enum_val,
+ });
+ new_decl.owns_tv = true;
+ errdefer sema.mod.deleteAnonDecl(&block.base, new_decl);
+
+ enum_obj.* = .{
+ .owner_decl = new_decl,
+ .fields = .{},
+ .node_offset = 0,
+ };
+ // Here we pre-allocate the maps using the decl arena.
+ try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len);
+ try new_decl.finalizeNewArena(&new_decl_arena);
+ return enum_ty;
}
fn getBuiltin(
@@ -11367,11 +11556,28 @@ fn typeHasOnePossibleValue(
}
return Value.initTag(.empty_struct_value);
},
+ .enum_numbered => {
+ const resolved_ty = try sema.resolveTypeFields(block, src, ty);
+ const enum_obj = resolved_ty.castTag(.enum_numbered).?.data;
+ if (enum_obj.fields.count() == 1) {
+ if (enum_obj.values.count() == 0) {
+ return Value.initTag(.zero); // auto-numbered
+ } else {
+ return enum_obj.values.keys()[0];
+ }
+ } else {
+ return null;
+ }
+ },
.enum_full => {
const resolved_ty = try sema.resolveTypeFields(block, src, ty);
- const enum_full = resolved_ty.castTag(.enum_full).?.data;
- if (enum_full.fields.count() == 1) {
- return enum_full.values.keys()[0];
+ const enum_obj = resolved_ty.castTag(.enum_full).?.data;
+ if (enum_obj.fields.count() == 1) {
+ if (enum_obj.values.count() == 0) {
+ return Value.initTag(.zero); // auto-numbered
+ } else {
+ return enum_obj.values.keys()[0];
+ }
} else {
return null;
}
diff --git a/src/codegen.zig b/src/codegen.zig
index 102f8d4985..6a605edca9 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -889,6 +889,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.atomic_load => try self.airAtomicLoad(inst),
.memcpy => try self.airMemcpy(inst),
.memset => try self.airMemset(inst),
+ .set_union_tag => try self.airSetUnionTag(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1543,6 +1544,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = switch (arch) {
+ else => return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
if (!self.liveness.operandDies(inst, op_index))
return false;
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 16b13db292..fc0c86b8f1 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -955,6 +955,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.atomic_load => try airAtomicLoad(f, inst),
.memset => try airMemset(f, inst),
.memcpy => try airMemcpy(f, inst),
+ .set_union_tag => try airSetUnionTag(f, inst),
.int_to_float,
.float_to_int,
@@ -2080,6 +2081,21 @@ fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue {
return CValue.none;
}
+fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
+ const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+ const union_ptr = try f.resolveInst(bin_op.lhs);
+ const new_tag = try f.resolveInst(bin_op.rhs);
+ const writer = f.object.writer();
+
+ try writer.writeAll("*");
+ try f.writeCValue(writer, union_ptr);
+ try writer.writeAll(" = ");
+ try f.writeCValue(writer, new_tag);
+ try writer.writeAll(";\n");
+
+ return CValue.none;
+}
+
fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
return switch (order) {
.Unordered => "memory_order_relaxed",
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index b15834c963..ab164b5d91 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -735,7 +735,7 @@ pub const DeclGen = struct {
},
.Enum => {
var buffer: Type.Payload.Bits = undefined;
- const int_ty = t.enumTagType(&buffer);
+ const int_ty = t.intTagType(&buffer);
const bit_count = int_ty.intInfo(self.module.getTarget()).bits;
return self.context.intType(bit_count);
},
@@ -812,6 +812,29 @@ pub const DeclGen = struct {
.False,
);
},
+ .Union => {
+ const union_obj = t.castTag(.@"union").?.data;
+ assert(union_obj.haveFieldTypes());
+
+ const enum_tag_ty = union_obj.tag_ty;
+ const enum_tag_llvm_ty = try self.llvmType(enum_tag_ty);
+
+ if (union_obj.onlyTagHasCodegenBits()) {
+ return enum_tag_llvm_ty;
+ }
+
+ const target = self.module.getTarget();
+ const most_aligned_field_index = union_obj.mostAlignedField(target);
+ const most_aligned_field = union_obj.fields.values()[most_aligned_field_index];
+ // TODO handle when the most aligned field is different than the
+ // biggest sized field.
+
+ const llvm_fields = [_]*const llvm.Type{
+ try self.llvmType(most_aligned_field.ty),
+ enum_tag_llvm_ty,
+ };
+ return self.context.structType(&llvm_fields, llvm_fields.len, .False);
+ },
.Fn => {
const ret_ty = try self.llvmType(t.fnReturnType());
const params_len = t.fnParamLen();
@@ -840,7 +863,6 @@ pub const DeclGen = struct {
.BoundFn => @panic("TODO remove BoundFn from the language"),
- .Union,
.Opaque,
.Frame,
.AnyFrame,
@@ -1131,7 +1153,7 @@ pub const DeclGen = struct {
var buffer: Type.Payload.Bits = undefined;
const int_ty = switch (ty.zigTypeTag()) {
.Int => ty,
- .Enum => ty.enumTagType(&buffer),
+ .Enum => ty.intTagType(&buffer),
.Float => {
if (!is_rmw_xchg) return null;
return dg.context.intType(@intCast(c_uint, ty.abiSize(target) * 8));
@@ -1281,6 +1303,7 @@ pub const FuncGen = struct {
.atomic_load => try self.airAtomicLoad(inst),
.memset => try self.airMemset(inst),
.memcpy => try self.airMemcpy(inst),
+ .set_union_tag => try self.airSetUnionTag(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1381,7 +1404,7 @@ pub const FuncGen = struct {
const int_ty = switch (operand_ty.zigTypeTag()) {
.Enum => blk: {
var buffer: Type.Payload.Bits = undefined;
- const int_ty = operand_ty.enumTagType(&buffer);
+ const int_ty = operand_ty.intTagType(&buffer);
break :blk int_ty;
},
.Int, .Bool, .Pointer, .ErrorSet => operand_ty,
@@ -1660,8 +1683,9 @@ pub const FuncGen = struct {
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
const struct_ptr = try self.resolveInst(struct_field.struct_operand);
+ const struct_ptr_ty = self.air.typeOf(struct_field.struct_operand);
const field_index = @intCast(c_uint, struct_field.field_index);
- return self.builder.buildStructGEP(struct_ptr, field_index, "");
+ return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index);
}
fn airStructFieldPtrIndex(self: *FuncGen, inst: Air.Inst.Index, field_index: c_uint) !?*const llvm.Value {
@@ -1670,7 +1694,8 @@ pub const FuncGen = struct {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const struct_ptr = try self.resolveInst(ty_op.operand);
- return self.builder.buildStructGEP(struct_ptr, field_index, "");
+ const struct_ptr_ty = self.air.typeOf(ty_op.operand);
+ return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index);
}
fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
@@ -2521,6 +2546,49 @@ pub const FuncGen = struct {
return null;
}
+ fn airSetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const union_ptr = try self.resolveInst(bin_op.lhs);
+ // TODO handle when onlyTagHasCodegenBits() == true
+ const new_tag = try self.resolveInst(bin_op.rhs);
+ const tag_field_ptr = self.builder.buildStructGEP(union_ptr, 1, "");
+
+ _ = self.builder.buildStore(new_tag, tag_field_ptr);
+ return null;
+ }
+
+ fn fieldPtr(
+ self: *FuncGen,
+ inst: Air.Inst.Index,
+ struct_ptr: *const llvm.Value,
+ struct_ptr_ty: Type,
+ field_index: c_uint,
+ ) !?*const llvm.Value {
+ const struct_ty = struct_ptr_ty.childType();
+ switch (struct_ty.zigTypeTag()) {
+ .Struct => return self.builder.buildStructGEP(struct_ptr, field_index, ""),
+ .Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index),
+ else => unreachable,
+ }
+ }
+
+ fn unionFieldPtr(
+ self: *FuncGen,
+ inst: Air.Inst.Index,
+ union_ptr: *const llvm.Value,
+ union_ty: Type,
+ field_index: c_uint,
+ ) !?*const llvm.Value {
+ const union_obj = union_ty.cast(Type.Payload.Union).?.data;
+ const field = &union_obj.fields.values()[field_index];
+ const result_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
+ if (!field.ty.hasCodeGenBits()) {
+ return null;
+ }
+ const union_field_ptr = self.builder.buildStructGEP(union_ptr, 0, "");
+ return self.builder.buildBitCast(union_field_ptr, result_llvm_ty, "");
+ }
+
fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value {
const id = llvm.lookupIntrinsicID(name.ptr, name.len);
assert(id != 0);
diff --git a/src/print_air.zig b/src/print_air.zig
index fa384baae0..e735d03bd3 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -130,6 +130,7 @@ const Writer = struct {
.ptr_ptr_elem_val,
.shl,
.shr,
+ .set_union_tag,
=> try w.writeBinOp(s, inst),
.is_null,
diff --git a/src/type.zig b/src/type.zig
index 48c65c1008..bb798959f4 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -124,6 +124,7 @@ pub const Type = extern union {
.enum_full,
.enum_nonexhaustive,
.enum_simple,
+ .enum_numbered,
.atomic_order,
.atomic_rmw_op,
.calling_convention,
@@ -874,6 +875,7 @@ pub const Type = extern union {
.@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct),
.@"union", .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union),
.enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple),
+ .enum_numbered => return self.copyPayloadShallow(allocator, Payload.EnumNumbered),
.enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull),
.@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque),
}
@@ -958,6 +960,10 @@ pub const Type = extern union {
const enum_simple = ty.castTag(.enum_simple).?.data;
return enum_simple.owner_decl.renderFullyQualifiedName(writer);
},
+ .enum_numbered => {
+ const enum_numbered = ty.castTag(.enum_numbered).?.data;
+ return enum_numbered.owner_decl.renderFullyQualifiedName(writer);
+ },
.@"opaque" => {
// TODO use declaration name
return writer.writeAll("opaque {}");
@@ -1268,6 +1274,7 @@ pub const Type = extern union {
.@"union",
.union_tagged,
.enum_simple,
+ .enum_numbered,
.enum_full,
.enum_nonexhaustive,
=> false, // TODO some of these should be `true` depending on their child types
@@ -1421,7 +1428,7 @@ pub const Type = extern union {
const enum_simple = self.castTag(.enum_simple).?.data;
return enum_simple.fields.count() >= 2;
},
- .enum_nonexhaustive => {
+ .enum_numbered, .enum_nonexhaustive => {
var buffer: Payload.Bits = undefined;
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.hasCodeGenBits();
@@ -1682,7 +1689,7 @@ pub const Type = extern union {
assert(biggest != 0);
return biggest;
},
- .enum_full, .enum_nonexhaustive, .enum_simple => {
+ .enum_full, .enum_nonexhaustive, .enum_simple, .enum_numbered => {
var buffer: Payload.Bits = undefined;
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.abiAlignment(target);
@@ -1781,7 +1788,7 @@ pub const Type = extern union {
}
return size;
},
- .enum_simple, .enum_full, .enum_nonexhaustive => {
+ .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => {
var buffer: Payload.Bits = undefined;
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.abiSize(target);
@@ -1948,7 +1955,7 @@ pub const Type = extern union {
.@"struct" => {
@panic("TODO bitSize struct");
},
- .enum_simple, .enum_full, .enum_nonexhaustive => {
+ .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => {
var buffer: Payload.Bits = undefined;
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.bitSize(target);
@@ -2094,23 +2101,6 @@ pub const Type = extern union {
};
}
- /// Asserts the type is an enum.
- pub fn intTagType(self: Type, buffer: *Payload.Bits) Type {
- switch (self.tag()) {
- .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty,
- .enum_simple => {
- const enum_simple = self.castTag(.enum_simple).?.data;
- const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count());
- buffer.* = .{
- .base = .{ .tag = .int_unsigned },
- .data = bits,
- };
- return Type.initPayload(&buffer.base);
- },
- else => unreachable,
- }
- }
-
pub fn isSinglePointer(self: Type) bool {
return switch (self.tag()) {
.single_const_pointer,
@@ -2363,48 +2353,6 @@ pub const Type = extern union {
}
}
- /// Returns if type can be used for a runtime variable
- pub fn isValidVarType(self: Type, is_extern: bool) bool {
- var ty = self;
- while (true) switch (ty.zigTypeTag()) {
- .Bool,
- .Int,
- .Float,
- .ErrorSet,
- .Enum,
- .Frame,
- .AnyFrame,
- => return true,
-
- .Opaque => return is_extern,
- .BoundFn,
- .ComptimeFloat,
- .ComptimeInt,
- .EnumLiteral,
- .NoReturn,
- .Type,
- .Void,
- .Undefined,
- .Null,
- => return false,
-
- .Optional => {
- var buf: Payload.ElemType = undefined;
- return ty.optionalChild(&buf).isValidVarType(is_extern);
- },
- .Pointer, .Array, .Vector => ty = ty.elemType(),
- .ErrorUnion => ty = ty.errorUnionPayload(),
-
- .Fn => @panic("TODO fn isValidVarType"),
- .Struct => {
- // TODO this is not always correct; introduce lazy value mechanism
- // and here we need to force a resolve of "type requires comptime".
- return true;
- },
- .Union => @panic("TODO union isValidVarType"),
- };
- }
-
pub fn childType(ty: Type) Type {
return switch (ty.tag()) {
.vector => ty.castTag(.vector).?.data.elem_type,
@@ -2530,6 +2478,15 @@ pub const Type = extern union {
}
}
+ /// Returns the tag type of a union, if the type is a union and it has a tag type.
+ /// Otherwise, returns `null`.
+ pub fn unionTagType(ty: Type) ?Type {
+ return switch (ty.tag()) {
+ .union_tagged => ty.castTag(.union_tagged).?.data.tag_ty,
+ else => null,
+ };
+ }
+
/// Asserts that the type is an error union.
pub fn errorUnionPayload(self: Type) Type {
return switch (self.tag()) {
@@ -3000,6 +2957,7 @@ pub const Type = extern union {
}
},
.enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty,
+ .enum_numbered => ty = ty.castTag(.enum_numbered).?.data.tag_ty,
.@"union" => {
return null; // TODO
},
@@ -3114,31 +3072,21 @@ pub const Type = extern union {
}
}
- /// Returns the integer tag type of the enum.
- pub fn enumTagType(ty: Type, buffer: *Payload.Bits) Type {
- switch (ty.tag()) {
- .enum_full, .enum_nonexhaustive => {
- const enum_full = ty.cast(Payload.EnumFull).?.data;
- return enum_full.tag_ty;
- },
+ /// Asserts the type is an enum or a union.
+ /// TODO support unions
+ pub fn intTagType(self: Type, buffer: *Payload.Bits) Type {
+ switch (self.tag()) {
+ .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty,
+ .enum_numbered => return self.castTag(.enum_numbered).?.data.tag_ty,
.enum_simple => {
- const enum_simple = ty.castTag(.enum_simple).?.data;
+ const enum_simple = self.castTag(.enum_simple).?.data;
+ const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count());
buffer.* = .{
.base = .{ .tag = .int_unsigned },
- .data = std.math.log2_int_ceil(usize, enum_simple.fields.count()),
+ .data = bits,
};
return Type.initPayload(&buffer.base);
},
- .atomic_order,
- .atomic_rmw_op,
- .calling_convention,
- .float_mode,
- .reduce_op,
- .call_options,
- .export_options,
- .extern_options,
- => @panic("TODO resolve std.builtin types"),
-
else => unreachable,
}
}
@@ -3156,10 +3104,8 @@ pub const Type = extern union {
const enum_full = ty.cast(Payload.EnumFull).?.data;
return enum_full.fields.count();
},
- .enum_simple => {
- const enum_simple = ty.castTag(.enum_simple).?.data;
- return enum_simple.fields.count();
- },
+ .enum_simple => return ty.castTag(.enum_simple).?.data.fields.count(),
+ .enum_numbered => return ty.castTag(.enum_numbered).?.data.fields.count(),
.atomic_order,
.atomic_rmw_op,
.calling_convention,
@@ -3185,6 +3131,10 @@ pub const Type = extern union {
const enum_simple = ty.castTag(.enum_simple).?.data;
return enum_simple.fields.keys()[field_index];
},
+ .enum_numbered => {
+ const enum_numbered = ty.castTag(.enum_numbered).?.data;
+ return enum_numbered.fields.keys()[field_index];
+ },
.atomic_order,
.atomic_rmw_op,
.calling_convention,
@@ -3209,6 +3159,10 @@ pub const Type = extern union {
const enum_simple = ty.castTag(.enum_simple).?.data;
return enum_simple.fields.getIndex(field_name);
},
+ .enum_numbered => {
+ const enum_numbered = ty.castTag(.enum_numbered).?.data;
+ return enum_numbered.fields.getIndex(field_name);
+ },
.atomic_order,
.atomic_rmw_op,
.calling_convention,
@@ -3252,6 +3206,15 @@ pub const Type = extern union {
return enum_full.values.getIndexContext(enum_tag, .{ .ty = tag_ty });
}
},
+ .enum_numbered => {
+ const enum_obj = ty.castTag(.enum_numbered).?.data;
+ const tag_ty = enum_obj.tag_ty;
+ if (enum_obj.values.count() == 0) {
+ return S.fieldWithRange(tag_ty, enum_tag, enum_obj.fields.count());
+ } else {
+ return enum_obj.values.getIndexContext(enum_tag, .{ .ty = tag_ty });
+ }
+ },
.enum_simple => {
const enum_simple = ty.castTag(.enum_simple).?.data;
const fields_len = enum_simple.fields.count();
@@ -3303,6 +3266,7 @@ pub const Type = extern union {
const enum_full = ty.cast(Payload.EnumFull).?.data;
return enum_full.srcLoc();
},
+ .enum_numbered => return ty.castTag(.enum_numbered).?.data.srcLoc(),
.enum_simple => {
const enum_simple = ty.castTag(.enum_simple).?.data;
return enum_simple.srcLoc();
@@ -3340,6 +3304,7 @@ pub const Type = extern union {
const enum_full = ty.cast(Payload.EnumFull).?.data;
return enum_full.owner_decl;
},
+ .enum_numbered => return ty.castTag(.enum_numbered).?.data.owner_decl,
.enum_simple => {
const enum_simple = ty.castTag(.enum_simple).?.data;
return enum_simple.owner_decl;
@@ -3397,6 +3362,15 @@ pub const Type = extern union {
return enum_full.values.containsContext(int, .{ .ty = tag_ty });
}
},
+ .enum_numbered => {
+ const enum_obj = ty.castTag(.enum_numbered).?.data;
+ const tag_ty = enum_obj.tag_ty;
+ if (enum_obj.values.count() == 0) {
+ return S.intInRange(tag_ty, int, enum_obj.fields.count());
+ } else {
+ return enum_obj.values.containsContext(int, .{ .ty = tag_ty });
+ }
+ },
.enum_simple => {
const enum_simple = ty.castTag(.enum_simple).?.data;
const fields_len = enum_simple.fields.count();
@@ -3534,6 +3508,7 @@ pub const Type = extern union {
@"union",
union_tagged,
enum_simple,
+ enum_numbered,
enum_full,
enum_nonexhaustive,
@@ -3642,6 +3617,7 @@ pub const Type = extern union {
.@"union", .union_tagged => Payload.Union,
.enum_full, .enum_nonexhaustive => Payload.EnumFull,
.enum_simple => Payload.EnumSimple,
+ .enum_numbered => Payload.EnumNumbered,
.empty_struct => Payload.ContainerScope,
};
}
@@ -3818,6 +3794,11 @@ pub const Type = extern union {
base: Payload = .{ .tag = .enum_simple },
data: *Module.EnumSimple,
};
+
+ pub const EnumNumbered = struct {
+ base: Payload = .{ .tag = .enum_numbered },
+ data: *Module.EnumNumbered,
+ };
};
pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {
@@ -3850,6 +3831,23 @@ pub const Type = extern union {
};
return Type.initPayload(&type_payload.base);
}
+
+ pub fn smallestUnsignedInt(arena: *Allocator, max: u64) !Type {
+ const bits = bits: {
+ if (max == 0) break :bits 0;
+ const base = std.math.log2(max);
+ const upper = (@as(u64, 1) << base) - 1;
+ break :bits base + @boolToInt(upper < max);
+ };
+ return switch (bits) {
+ 1 => initTag(.u1),
+ 8 => initTag(.u8),
+ 16 => initTag(.u16),
+ 32 => initTag(.u32),
+ 64 => initTag(.u64),
+ else => return Tag.int_unsigned.create(arena, bits),
+ };
+ }
};
pub const CType = enum {
diff --git a/test/behavior/union.zig b/test/behavior/union.zig
index 14b5e374dd..6b8705e044 100644
--- a/test/behavior/union.zig
+++ b/test/behavior/union.zig
@@ -2,3 +2,15 @@ const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const Tag = std.meta.Tag;
+
+const Foo = union {
+ float: f64,
+ int: i32,
+};
+
+test "basic unions" {
+ var foo = Foo{ .int = 1 };
+ try expect(foo.int == 1);
+ foo = Foo{ .float = 12.34 };
+ try expect(foo.float == 12.34);
+}
diff --git a/test/behavior/union_stage1.zig b/test/behavior/union_stage1.zig
index 086bd981cd..5741858d51 100644
--- a/test/behavior/union_stage1.zig
+++ b/test/behavior/union_stage1.zig
@@ -39,13 +39,6 @@ const Foo = union {
int: i32,
};
-test "basic unions" {
- var foo = Foo{ .int = 1 };
- try expect(foo.int == 1);
- foo = Foo{ .float = 12.34 };
- try expect(foo.float == 12.34);
-}
-
test "comptime union field access" {
comptime {
var foo = Foo{ .int = 0 };
From c2a7542df5e9e93289a5d487ba3bbc37c12ffc11 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Mon, 27 Sep 2021 21:39:27 -0700
Subject: [PATCH 122/160] ci: azure: run build steps independently to save ram
Azure is hitting OOM on test-toolchain. This commit is trying to
coast for another week until we switch to Drone CI for this job.
---
ci/azure/linux_script | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/ci/azure/linux_script b/ci/azure/linux_script
index d24663160a..c796f7a2f3 100755
--- a/ci/azure/linux_script
+++ b/ci/azure/linux_script
@@ -71,9 +71,22 @@ make $JOBS install
release/bin/zig test ../test/behavior.zig -fno-stage1 -fLLVM -I ../test
-release/bin/zig build test-toolchain -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-std -Denable-qemu -Denable-wasmtime
-release/bin/zig build docs -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-behavior -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-compiler-rt -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-std -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-minilibc -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-test-compare-output -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-standalone -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-stack-traces -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-cli -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-asm-link -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-runtime-safety -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-translate-c -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-run-translated-c -Denable-qemu -Denable-wasmtime
+release/bin/zig build docs -Denable-qemu -Denable-wasmtime
+release/bin/zig build # test building self-hosted without LLVM
+release/bin/zig build test-fmt -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-stage2 -Denable-qemu -Denable-wasmtime
# Look for HTML errors.
tidy -qe ../zig-cache/langref.html
From 09e1f37cb6a8e0df4c521f4b76eab07f0c811852 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Mon, 27 Sep 2021 23:11:00 -0700
Subject: [PATCH 123/160] stage2: implement union coercion to its own tag
* AIR: add `get_union_tag` instruction
- implement in LLVM backend
* Sema: implement == and != for union and enum literal
- Also implement coercion from union to its own tag type
* Value: implement hashing for union values
The motivating example is this snippet:
comptime assert(@typeInfo(T) == .Float);
This was the next blocker for stage2 building compiler-rt.
Now it is switch at compile-time on an integer.
---
src/Air.zig | 4 ++
src/Liveness.zig | 1 +
src/Sema.zig | 82 +++++++++++++++++++++++++++++-----
src/codegen.zig | 9 ++++
src/codegen/c.zig | 17 +++++++
src/codegen/llvm.zig | 13 ++++++
src/print_air.zig | 1 +
src/type.zig | 8 ++++
src/value.zig | 15 ++++++-
test/behavior/union.zig | 18 ++++++++
test/behavior/union_stage1.zig | 27 +++--------
11 files changed, 162 insertions(+), 33 deletions(-)
diff --git a/src/Air.zig b/src/Air.zig
index 40070dccfb..b4552f9d7b 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -290,6 +290,9 @@ pub const Inst = struct {
/// Result type is always void.
/// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value.
set_union_tag,
+ /// Given a tagged union value, get its tag value.
+ /// Uses the `ty_op` field.
+ get_union_tag,
/// Given a slice value, return the length.
/// Result type is always usize.
/// Uses the `ty_op` field.
@@ -630,6 +633,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.array_to_slice,
.float_to_int,
.int_to_float,
+ .get_union_tag,
=> return air.getRefType(datas[inst].ty_op.ty),
.loop,
diff --git a/src/Liveness.zig b/src/Liveness.zig
index 9a7126d135..a9ff586aeb 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -297,6 +297,7 @@ fn analyzeInst(
.array_to_slice,
.float_to_int,
.int_to_float,
+ .get_union_tag,
=> {
const o = inst_datas[inst].ty_op;
return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
diff --git a/src/Sema.zig b/src/Sema.zig
index f076389797..b669cdb979 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -1349,7 +1349,13 @@ fn zirUnionDecl(
errdefer new_decl_arena.deinit();
const union_obj = try new_decl_arena.allocator.create(Module.Union);
- const union_ty = try Type.Tag.@"union".create(&new_decl_arena.allocator, union_obj);
+ const type_tag: Type.Tag = if (small.has_tag_type or small.auto_enum_tag) .union_tagged else .@"union";
+ const union_payload = try new_decl_arena.allocator.create(Type.Payload.Union);
+ union_payload.* = .{
+ .base = .{ .tag = type_tag },
+ .data = union_obj,
+ };
+ const union_ty = Type.initPayload(&union_payload.base);
const union_val = try Value.Tag.ty.create(&new_decl_arena.allocator, union_ty);
const type_name = try sema.createTypeName(block, small.name_strategy);
const new_decl = try sema.mod.createAnonymousDeclNamed(&block.base, .{
@@ -6477,10 +6483,11 @@ fn zirCmpEq(
const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty;
return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type});
}
- if (((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
- (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
- {
- return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
+ if (lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) {
+ return sema.analyzeCmpUnionTag(block, rhs, rhs_src, lhs, lhs_src, op);
+ }
+ if (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union) {
+ return sema.analyzeCmpUnionTag(block, lhs, lhs_src, rhs, rhs_src, op);
}
if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
const runtime_src: LazySrcLoc = src: {
@@ -6521,6 +6528,28 @@ fn zirCmpEq(
return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, true);
}
+fn analyzeCmpUnionTag(
+ sema: *Sema,
+ block: *Scope.Block,
+ un: Air.Inst.Ref,
+ un_src: LazySrcLoc,
+ tag: Air.Inst.Ref,
+ tag_src: LazySrcLoc,
+ op: std.math.CompareOperator,
+) CompileError!Air.Inst.Ref {
+ const union_ty = sema.typeOf(un);
+ const union_tag_ty = union_ty.unionTagType() orelse {
+ // TODO note at declaration site that says "union foo is not tagged"
+ return sema.mod.fail(&block.base, un_src, "comparison of union and enum literal is only valid for tagged union types", .{});
+ };
+ // Coerce both the union and the tag to the union's tag type, and then execute the
+ // enum comparison codepath.
+ const coerced_tag = try sema.coerce(block, union_tag_ty, tag, tag_src);
+ const coerced_union = try sema.coerce(block, union_tag_ty, un, un_src);
+
+ return sema.cmpSelf(block, coerced_union, coerced_tag, op, un_src, tag_src);
+}
+
/// Only called for non-equality operators. See also `zirCmpEq`.
fn zirCmp(
sema: *Sema,
@@ -6567,10 +6596,21 @@ fn analyzeCmp(
@tagName(op), resolved_type,
});
}
-
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+ return sema.cmpSelf(block, casted_lhs, casted_rhs, op, lhs_src, rhs_src);
+}
+fn cmpSelf(
+ sema: *Sema,
+ block: *Scope.Block,
+ casted_lhs: Air.Inst.Ref,
+ casted_rhs: Air.Inst.Ref,
+ op: std.math.CompareOperator,
+ lhs_src: LazySrcLoc,
+ rhs_src: LazySrcLoc,
+) CompileError!Air.Inst.Ref {
+ const resolved_type = sema.typeOf(casted_lhs);
const runtime_src: LazySrcLoc = src: {
if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
if (lhs_val.isUndef()) return sema.addConstUndef(resolved_type);
@@ -9919,9 +9959,9 @@ fn coerce(
}
}
},
- .Enum => {
- // enum literal to enum
- if (inst_ty.zigTypeTag() == .EnumLiteral) {
+ .Enum => switch (inst_ty.zigTypeTag()) {
+ .EnumLiteral => {
+ // enum literal to enum
const val = try sema.resolveConstValue(block, inst_src, inst);
const bytes = val.castTag(.enum_literal).?.data;
const resolved_dest_type = try sema.resolveTypeFields(block, inst_src, dest_type);
@@ -9948,7 +9988,15 @@ fn coerce(
resolved_dest_type,
try Value.Tag.enum_field_index.create(arena, @intCast(u32, field_index)),
);
- }
+ },
+ .Union => blk: {
+ // union to its own tag type
+ const union_tag_ty = inst_ty.unionTagType() orelse break :blk;
+ if (union_tag_ty.eql(dest_type)) {
+ return sema.unionToTag(block, dest_type, inst, inst_src);
+ }
+ },
+ else => {},
},
.ErrorUnion => {
// T to E!T or E to E!T
@@ -10802,6 +10850,20 @@ fn wrapErrorUnion(
}
}
+fn unionToTag(
+ sema: *Sema,
+ block: *Scope.Block,
+ dest_type: Type,
+ un: Air.Inst.Ref,
+ un_src: LazySrcLoc,
+) !Air.Inst.Ref {
+ if (try sema.resolveMaybeUndefVal(block, un_src, un)) |un_val| {
+ return sema.addConstant(dest_type, un_val.unionTag());
+ }
+ try sema.requireRuntimeBlock(block, un_src);
+ return block.addTyOp(.get_union_tag, dest_type, un);
+}
+
fn resolvePeerTypes(
sema: *Sema,
block: *Scope.Block,
diff --git a/src/codegen.zig b/src/codegen.zig
index 6a605edca9..4eda3f2594 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -890,6 +890,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.memcpy => try self.airMemcpy(inst),
.memset => try self.airMemset(inst),
.set_union_tag => try self.airSetUnionTag(inst),
+ .get_union_tag => try self.airGetUnionTag(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1552,6 +1553,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+ }
+
fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
if (!self.liveness.operandDies(inst, op_index))
return false;
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index fc0c86b8f1..a6534b1eba 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -956,6 +956,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.memset => try airMemset(f, inst),
.memcpy => try airMemcpy(f, inst),
.set_union_tag => try airSetUnionTag(f, inst),
+ .get_union_tag => try airGetUnionTag(f, inst),
.int_to_float,
.float_to_int,
@@ -2096,6 +2097,22 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
return CValue.none;
}
+fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue {
+ if (f.liveness.isUnused(inst))
+ return CValue.none;
+
+ const inst_ty = f.air.typeOfIndex(inst);
+ const local = try f.allocLocal(inst_ty, .Const);
+ const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+ const writer = f.object.writer();
+ const operand = try f.resolveInst(ty_op.operand);
+
+ try writer.writeAll("get_union_tag(");
+ try f.writeCValue(writer, operand);
+ try writer.writeAll(");\n");
+ return local;
+}
+
fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 {
return switch (order) {
.Unordered => "memory_order_relaxed",
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index ab164b5d91..4a0d218ead 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1304,6 +1304,7 @@ pub const FuncGen = struct {
.memset => try self.airMemset(inst),
.memcpy => try self.airMemcpy(inst),
.set_union_tag => try self.airSetUnionTag(inst),
+ .get_union_tag => try self.airGetUnionTag(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2557,6 +2558,18 @@ pub const FuncGen = struct {
return null;
}
+ fn airGetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst))
+ return null;
+
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const un_ty = self.air.typeOf(ty_op.operand);
+ const un = try self.resolveInst(ty_op.operand);
+
+ _ = un_ty; // TODO handle when onlyTagHasCodegenBits() == true and other union forms
+ return self.builder.buildExtractValue(un, 1, "");
+ }
+
fn fieldPtr(
self: *FuncGen,
inst: Air.Inst.Index,
diff --git a/src/print_air.zig b/src/print_air.zig
index e735d03bd3..2a7538f81a 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -179,6 +179,7 @@ const Writer = struct {
.array_to_slice,
.int_to_float,
.float_to_int,
+ .get_union_tag,
=> try w.writeTyOp(s, inst),
.block,
diff --git a/src/type.zig b/src/type.zig
index bb798959f4..781fe74d45 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -2487,6 +2487,12 @@ pub const Type = extern union {
};
}
+ pub fn unionFieldType(ty: Type, enum_tag: Value) Type {
+ const union_obj = ty.cast(Payload.Union).?.data;
+ const index = union_obj.tag_ty.enumTagFieldIndex(enum_tag).?;
+ return union_obj.fields.values()[index].ty;
+ }
+
/// Asserts that the type is an error union.
pub fn errorUnionPayload(self: Type) Type {
return switch (self.tag()) {
@@ -3801,6 +3807,8 @@ pub const Type = extern union {
};
};
+ pub const @"bool" = initTag(.bool);
+
pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {
assert(d.host_size == 0 or d.bit_offset < d.host_size * 8);
diff --git a/src/value.zig b/src/value.zig
index cb5d211b1e..69f8945e01 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -1275,7 +1275,12 @@ pub const Value = extern union {
}
},
.Union => {
- @panic("TODO implement hashing union values");
+ const union_obj = val.castTag(.@"union").?.data;
+ if (ty.unionTagType()) |tag_ty| {
+ union_obj.tag.hash(tag_ty, hasher);
+ }
+ const active_field_ty = ty.unionFieldType(union_obj.tag);
+ union_obj.val.hash(active_field_ty, hasher);
},
.Fn => {
@panic("TODO implement hashing function values");
@@ -1431,6 +1436,14 @@ pub const Value = extern union {
}
}
+ pub fn unionTag(val: Value) Value {
+ switch (val.tag()) {
+ .undef => return val,
+ .@"union" => return val.castTag(.@"union").?.data.tag,
+ else => unreachable,
+ }
+ }
+
/// Returns a pointer to the element value at the index.
pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value {
if (self.castTag(.elem_ptr)) |elem_ptr| {
diff --git a/test/behavior/union.zig b/test/behavior/union.zig
index 6b8705e044..afefa7cf85 100644
--- a/test/behavior/union.zig
+++ b/test/behavior/union.zig
@@ -14,3 +14,21 @@ test "basic unions" {
foo = Foo{ .float = 12.34 };
try expect(foo.float == 12.34);
}
+
+test "init union with runtime value" {
+ var foo: Foo = undefined;
+
+ setFloat(&foo, 12.34);
+ try expect(foo.float == 12.34);
+
+ setInt(&foo, 42);
+ try expect(foo.int == 42);
+}
+
+fn setFloat(foo: *Foo, x: f64) void {
+ foo.* = Foo{ .float = x };
+}
+
+fn setInt(foo: *Foo, x: i32) void {
+ foo.* = Foo{ .int = x };
+}
diff --git a/test/behavior/union_stage1.zig b/test/behavior/union_stage1.zig
index 5741858d51..725d7bd028 100644
--- a/test/behavior/union_stage1.zig
+++ b/test/behavior/union_stage1.zig
@@ -49,24 +49,6 @@ test "comptime union field access" {
}
}
-test "init union with runtime value" {
- var foo: Foo = undefined;
-
- setFloat(&foo, 12.34);
- try expect(foo.float == 12.34);
-
- setInt(&foo, 42);
- try expect(foo.int == 42);
-}
-
-fn setFloat(foo: *Foo, x: f64) void {
- foo.* = Foo{ .float = x };
-}
-
-fn setInt(foo: *Foo, x: i32) void {
- foo.* = Foo{ .int = x };
-}
-
const FooExtern = extern union {
float: f64,
int: i32,
@@ -185,12 +167,13 @@ test "union field access gives the enum values" {
}
test "cast union to tag type of union" {
- try testCastUnionToTag(TheUnion{ .B = 1234 });
- comptime try testCastUnionToTag(TheUnion{ .B = 1234 });
+ try testCastUnionToTag();
+ comptime try testCastUnionToTag();
}
-fn testCastUnionToTag(x: TheUnion) !void {
- try expect(@as(TheTag, x) == TheTag.B);
+fn testCastUnionToTag() !void {
+ var u = TheUnion{ .B = 1234 };
+ try expect(@as(TheTag, u) == TheTag.B);
}
test "cast tag type of union to union" {
From 60b6e74468570a124f602a62b6bd2da95ba8c17c Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 09:50:25 -0700
Subject: [PATCH 124/160] ci: fix typo introduced in earlier commit
c2a7542df5e9e93289a5d487ba3bbc37c12ffc11 introduced a typo in the linux
CI script.
---
ci/azure/linux_script | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/ci/azure/linux_script b/ci/azure/linux_script
index c796f7a2f3..3a1f8b8928 100755
--- a/ci/azure/linux_script
+++ b/ci/azure/linux_script
@@ -71,22 +71,22 @@ make $JOBS install
release/bin/zig test ../test/behavior.zig -fno-stage1 -fLLVM -I ../test
-release/bin/zig build test-behavior -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-compiler-rt -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-std -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-minilibc -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-test-compare-output -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-standalone -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-stack-traces -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-cli -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-asm-link -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-runtime-safety -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-translate-c -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-run-translated-c -Denable-qemu -Denable-wasmtime
-release/bin/zig build docs -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-behavior -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-compiler-rt -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-std -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-minilibc -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-compare-output -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-standalone -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-stack-traces -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-cli -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-asm-link -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-runtime-safety -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-translate-c -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-run-translated-c -Denable-qemu -Denable-wasmtime
+release/bin/zig build docs -Denable-qemu -Denable-wasmtime
release/bin/zig build # test building self-hosted without LLVM
-release/bin/zig build test-fmt -Denable-qemu -Denable-wasmtime
-release/bin/zig build test-stage2 -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-fmt -Denable-qemu -Denable-wasmtime
+release/bin/zig build test-stage2 -Denable-qemu -Denable-wasmtime
# Look for HTML errors.
tidy -qe ../zig-cache/langref.html
From 1cc5d4e758a95be373756e7c32f9bb46d21633c9 Mon Sep 17 00:00:00 2001
From: Martin Wickham
Date: Tue, 28 Sep 2021 12:00:35 -0500
Subject: [PATCH 125/160] Stage 2: Support inst.func() syntax (#9827)
* Merge call zir instructions to make space for field_call
* Fix bug with comptime known anytype args
* Delete the param_type zir instruction
* Move some passing tests to stage 2
* Implement a.b() function calls
* Add field_call_bind support for call and field builtins
---
src/AstGen.zig | 161 +++++++++++-----
src/Sema.zig | 294 ++++++++++++++++++++++--------
src/Zir.zig | 77 ++++----
src/print_zir.zig | 42 +++--
src/type.zig | 62 ++++++-
src/value.zig | 20 ++
test/behavior.zig | 1 +
test/behavior/eval.zig | 28 +++
test/behavior/eval_stage1.zig | 28 ---
test/behavior/generics.zig | 16 ++
test/behavior/generics_stage1.zig | 16 --
test/behavior/member_func.zig | 103 +++++++++++
12 files changed, 629 insertions(+), 219 deletions(-)
create mode 100644 test/behavior/member_func.zig
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 469e77037a..15594ac27c 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -56,6 +56,7 @@ fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
u32 => @field(extra, field.name),
Zir.Inst.Ref => @enumToInt(@field(extra, field.name)),
i32 => @bitCast(u32, @field(extra, field.name)),
+ Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)),
else => @compileError("bad field type"),
});
}
@@ -1934,11 +1935,14 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
// in the above while loop.
const zir_tags = gz.astgen.instructions.items(.tag);
switch (zir_tags[inst]) {
- // For some instructions, swap in a slightly different ZIR tag
+ // For some instructions, modify the zir data
// so we can avoid a separate ensure_result_used instruction.
- .call_chkused => unreachable,
.call => {
- zir_tags[inst] = .call_chkused;
+ const extra_index = gz.astgen.instructions.items(.data)[inst].pl_node.payload_index;
+ const slot = &gz.astgen.extra.items[extra_index];
+ var flags = @bitCast(Zir.Inst.Call.Flags, slot.*);
+ flags.ensure_result_used = true;
+ slot.* = @bitCast(u32, flags);
break :b true;
},
@@ -1976,9 +1980,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.bool_br_and,
.bool_br_or,
.bool_not,
- .call_compile_time,
- .call_nosuspend,
- .call_async,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -1996,8 +1997,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.elem_val_node,
.field_ptr,
.field_val,
+ .field_call_bind,
.field_ptr_named,
.field_val_named,
+ .field_call_bind_named,
.func,
.func_inferred,
.int,
@@ -2012,7 +2015,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.mod_rem,
.mul,
.mulwrap,
- .param_type,
.ref,
.shl,
.shr,
@@ -4968,6 +4970,21 @@ fn fieldAccess(
scope: *Scope,
rl: ResultLoc,
node: Ast.Node.Index,
+) InnerError!Zir.Inst.Ref {
+ if (rl == .ref) {
+ return addFieldAccess(.field_ptr, gz, scope, .ref, node);
+ } else {
+ const access = try addFieldAccess(.field_val, gz, scope, .none_or_ref, node);
+ return rvalue(gz, rl, access, node);
+ }
+}
+
+fn addFieldAccess(
+ tag: Zir.Inst.Tag,
+ gz: *GenZir,
+ scope: *Scope,
+ lhs_rl: ResultLoc,
+ node: Ast.Node.Index,
) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const tree = astgen.tree;
@@ -4978,16 +4995,11 @@ fn fieldAccess(
const dot_token = main_tokens[node];
const field_ident = dot_token + 1;
const str_index = try astgen.identAsString(field_ident);
- switch (rl) {
- .ref => return gz.addPlNode(.field_ptr, node, Zir.Inst.Field{
- .lhs = try expr(gz, scope, .ref, object_node),
- .field_name_start = str_index,
- }),
- else => return rvalue(gz, rl, try gz.addPlNode(.field_val, node, Zir.Inst.Field{
- .lhs = try expr(gz, scope, .none_or_ref, object_node),
- .field_name_start = str_index,
- }), node),
- }
+
+ return gz.addPlNode(tag, node, Zir.Inst.Field{
+ .lhs = try expr(gz, scope, lhs_rl, object_node),
+ .field_name_start = str_index,
+ });
}
fn arrayAccess(
@@ -7169,16 +7181,15 @@ fn builtinCall(
return rvalue(gz, rl, result, node);
},
.field => {
- const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]);
if (rl == .ref) {
return gz.addPlNode(.field_ptr_named, node, Zir.Inst.FieldNamed{
.lhs = try expr(gz, scope, .ref, params[0]),
- .field_name = field_name,
+ .field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]),
});
}
const result = try gz.addPlNode(.field_val_named, node, Zir.Inst.FieldNamed{
.lhs = try expr(gz, scope, .none, params[0]),
- .field_name = field_name,
+ .field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]),
});
return rvalue(gz, rl, result, node);
},
@@ -7554,7 +7565,7 @@ fn builtinCall(
},
.call => {
const options = try comptimeExpr(gz, scope, .{ .ty = .call_options_type }, params[0]);
- const callee = try expr(gz, scope, .none, params[1]);
+ const callee = try calleeExpr(gz, scope, params[1]);
const args = try expr(gz, scope, .none, params[2]);
const result = try gz.addPlNode(.builtin_call, node, Zir.Inst.BuiltinCall{
.options = options,
@@ -7897,20 +7908,16 @@ fn callExpr(
call: Ast.full.Call,
) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
- const lhs = try expr(gz, scope, .none, call.ast.fn_expr);
+
+ const callee = try calleeExpr(gz, scope, call.ast.fn_expr);
const args = try astgen.gpa.alloc(Zir.Inst.Ref, call.ast.params.len);
defer astgen.gpa.free(args);
for (call.ast.params) |param_node, i| {
- const param_type = try gz.add(.{
- .tag = .param_type,
- .data = .{ .param_type = .{
- .callee = lhs,
- .param_index = @intCast(u32, i),
- } },
- });
- args[i] = try expr(gz, scope, .{ .coerced_ty = param_type }, param_node);
+ // Parameters are always temporary values, they have no
+ // meaningful result location. Sema will coerce them.
+ args[i] = try expr(gz, scope, .none, param_node);
}
const modifier: std.builtin.CallOptions.Modifier = blk: {
@@ -7925,20 +7932,72 @@ fn callExpr(
}
break :blk .auto;
};
- const result: Zir.Inst.Ref = res: {
- const tag: Zir.Inst.Tag = switch (modifier) {
- .auto => .call,
- .async_kw => .call_async,
- .never_tail => unreachable,
- .never_inline => unreachable,
- .no_async => .call_nosuspend,
- .always_tail => unreachable,
- .always_inline => unreachable,
- .compile_time => .call_compile_time,
- };
- break :res try gz.addCall(tag, lhs, args, node);
- };
- return rvalue(gz, rl, result, node); // TODO function call with result location
+ const call_inst = try gz.addCall(modifier, callee, args, node);
+ return rvalue(gz, rl, call_inst, node); // TODO function call with result location
+}
+
+/// calleeExpr generates the function part of a call expression (f in f(x)), or the
+/// callee argument to the @call() builtin. If the lhs is a field access or the
+/// @field() builtin, we need to generate a special field_call_bind instruction
+/// instead of the normal field_val or field_ptr. If this is a inst.func() call,
+/// this instruction will capture the value of the first argument before evaluating
+/// the other arguments. We need to use .ref here to guarantee we will be able to
+/// promote an lvalue to an address if the first parameter requires it. This
+/// unfortunately also means we need to take a reference to any types on the lhs.
+fn calleeExpr(
+ gz: *GenZir,
+ scope: *Scope,
+ node: Ast.Node.Index,
+) InnerError!Zir.Inst.Ref {
+ const astgen = gz.astgen;
+ const tree = astgen.tree;
+
+ const tag = tree.nodes.items(.tag)[node];
+ switch (tag) {
+ .field_access => return addFieldAccess(.field_call_bind, gz, scope, .ref, node),
+
+ .builtin_call_two,
+ .builtin_call_two_comma,
+ .builtin_call,
+ .builtin_call_comma,
+ => {
+ const node_datas = tree.nodes.items(.data);
+ const main_tokens = tree.nodes.items(.main_token);
+ const builtin_token = main_tokens[node];
+ const builtin_name = tree.tokenSlice(builtin_token);
+
+ var inline_params: [2]Ast.Node.Index = undefined;
+ var params: []Ast.Node.Index = switch (tag) {
+ .builtin_call,
+ .builtin_call_comma,
+ => tree.extra_data[node_datas[node].lhs..node_datas[node].rhs],
+
+ .builtin_call_two,
+ .builtin_call_two_comma,
+ => blk: {
+ inline_params = .{ node_datas[node].lhs, node_datas[node].rhs };
+ const len: usize = if (inline_params[0] == 0) @as(usize, 0) else if (inline_params[1] == 0) @as(usize, 1) else @as(usize, 2);
+ break :blk inline_params[0..len];
+ },
+
+ else => unreachable,
+ };
+
+ // If anything is wrong, fall back to builtinCall.
+ // It will emit any necessary compile errors and notes.
+ if (std.mem.eql(u8, builtin_name, "@field") and params.len == 2) {
+ const lhs = try expr(gz, scope, .ref, params[0]);
+ const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]);
+ return gz.addPlNode(.field_call_bind_named, node, Zir.Inst.FieldNamed{
+ .lhs = lhs,
+ .field_name = field_name,
+ });
+ }
+
+ return builtinCall(gz, scope, .none, node, params);
+ },
+ else => return expr(gz, scope, .none, node),
+ }
}
pub const simple_types = std.ComptimeStringMap(Zir.Inst.Ref, .{
@@ -9607,7 +9666,7 @@ const GenZir = struct {
fn addCall(
gz: *GenZir,
- tag: Zir.Inst.Tag,
+ modifier: std.builtin.CallOptions.Modifier,
callee: Zir.Inst.Ref,
args: []const Zir.Inst.Ref,
/// Absolute node index. This function does the conversion to offset from Decl.
@@ -9616,20 +9675,24 @@ const GenZir = struct {
assert(callee != .none);
assert(src_node != 0);
const gpa = gz.astgen.gpa;
+ const Call = Zir.Inst.Call;
try gz.instructions.ensureUnusedCapacity(gpa, 1);
try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
- try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Call).Struct.fields.len +
+ try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Call).Struct.fields.len +
args.len);
- const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Call{
+ const payload_index = gz.astgen.addExtraAssumeCapacity(Call{
.callee = callee,
- .args_len = @intCast(u32, args.len),
+ .flags = .{
+ .packed_modifier = @intCast(Call.Flags.PackedModifier, @enumToInt(modifier)),
+ .args_len = @intCast(Call.Flags.PackedArgsLen, args.len),
+ },
});
gz.astgen.appendRefsAssumeCapacity(args);
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
gz.astgen.instructions.appendAssumeCapacity(.{
- .tag = tag,
+ .tag = .call,
.data = .{ .pl_node = .{
.src_node = gz.nodeIndexToRelative(src_node),
.payload_index = payload_index,
diff --git a/src/Sema.zig b/src/Sema.zig
index b669cdb979..35a434eb35 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -185,11 +185,7 @@ pub fn analyzeBody(
.bool_br_and => try sema.zirBoolBr(block, inst, false),
.bool_br_or => try sema.zirBoolBr(block, inst, true),
.c_import => try sema.zirCImport(block, inst),
- .call => try sema.zirCall(block, inst, .auto, false),
- .call_chkused => try sema.zirCall(block, inst, .auto, true),
- .call_compile_time => try sema.zirCall(block, inst, .compile_time, false),
- .call_nosuspend => try sema.zirCall(block, inst, .no_async, false),
- .call_async => try sema.zirCall(block, inst, .async_kw, false),
+ .call => try sema.zirCall(block, inst),
.closure_get => try sema.zirClosureGet(block, inst),
.cmp_lt => try sema.zirCmp(block, inst, .lt),
.cmp_lte => try sema.zirCmp(block, inst, .lte),
@@ -223,6 +219,8 @@ pub fn analyzeBody(
.field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
.field_val => try sema.zirFieldVal(block, inst),
.field_val_named => try sema.zirFieldValNamed(block, inst),
+ .field_call_bind => try sema.zirFieldCallBind(block, inst),
+ .field_call_bind_named => try sema.zirFieldCallBindNamed(block, inst),
.func => try sema.zirFunc(block, inst, false),
.func_inferred => try sema.zirFunc(block, inst, true),
.import => try sema.zirImport(block, inst),
@@ -244,7 +242,6 @@ pub fn analyzeBody(
.optional_payload_unsafe => try sema.zirOptionalPayload(block, inst, false),
.optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, inst, false),
.optional_type => try sema.zirOptionalType(block, inst),
- .param_type => try sema.zirParamType(block, inst),
.ptr_type => try sema.zirPtrType(block, inst),
.ptr_type_simple => try sema.zirPtrTypeSimple(block, inst),
.ref => try sema.zirRef(block, inst),
@@ -2031,45 +2028,6 @@ fn zirStoreNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
return sema.storePtr(block, src, ptr, value);
}
-fn zirParamType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- const tracy = trace(@src());
- defer tracy.end();
-
- const src = sema.src;
- const fn_inst_src = sema.src;
-
- const inst_data = sema.code.instructions.items(.data)[inst].param_type;
- const fn_inst = sema.resolveInst(inst_data.callee);
- const fn_inst_ty = sema.typeOf(fn_inst);
- const param_index = inst_data.param_index;
-
- const fn_ty: Type = switch (fn_inst_ty.zigTypeTag()) {
- .Fn => fn_inst_ty,
- .BoundFn => {
- return sema.mod.fail(&block.base, fn_inst_src, "TODO implement zirParamType for method call syntax", .{});
- },
- else => {
- return sema.mod.fail(&block.base, fn_inst_src, "expected function, found '{}'", .{fn_inst_ty});
- },
- };
-
- const param_count = fn_ty.fnParamLen();
- if (param_index >= param_count) {
- if (fn_ty.fnIsVarArgs()) {
- return sema.addType(Type.initTag(.var_args_param));
- }
- return sema.mod.fail(&block.base, src, "arg index {d} out of bounds; '{}' has {d} argument(s)", .{
- param_index,
- fn_ty,
- param_count,
- });
- }
-
- // TODO support generic functions
- const param_type = fn_ty.fnParamType(param_index);
- return sema.addType(param_type);
-}
-
fn zirStr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@@ -2786,8 +2744,6 @@ fn zirCall(
sema: *Sema,
block: *Scope.Block,
inst: Zir.Inst.Index,
- modifier: std.builtin.CallOptions.Modifier,
- ensure_result_used: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@@ -2796,14 +2752,31 @@ fn zirCall(
const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node };
const call_src = inst_data.src();
const extra = sema.code.extraData(Zir.Inst.Call, inst_data.payload_index);
- const args = sema.code.refSlice(extra.end, extra.data.args_len);
+ const args = sema.code.refSlice(extra.end, extra.data.flags.args_len);
- const func = sema.resolveInst(extra.data.callee);
- // TODO handle function calls of generic functions
- const resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len);
- for (args) |zir_arg, i| {
- // the args are already casted to the result of a param type instruction.
- resolved_args[i] = sema.resolveInst(zir_arg);
+ const modifier = @intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier);
+ const ensure_result_used = extra.data.flags.ensure_result_used;
+
+ var func = sema.resolveInst(extra.data.callee);
+ var resolved_args: []Air.Inst.Ref = undefined;
+
+ const func_type = sema.typeOf(func);
+
+ // Desugar bound functions here
+ if (func_type.tag() == .bound_fn) {
+ const bound_func = try sema.resolveValue(block, func_src, func);
+ const bound_data = &bound_func.cast(Value.Payload.BoundFn).?.data;
+ func = bound_data.func_inst;
+ resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len + 1);
+ resolved_args[0] = bound_data.arg0_inst;
+ for (args) |zir_arg, i| {
+ resolved_args[i + 1] = sema.resolveInst(zir_arg);
+ }
+ } else {
+ resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len);
+ for (args) |zir_arg, i| {
+ resolved_args[i] = sema.resolveInst(zir_arg);
+ }
}
return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args);
@@ -3334,14 +3307,16 @@ fn analyzeCall(
}
const arg_src = call_src; // TODO: better source location
const arg = uncasted_args[arg_i];
- if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| {
- const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val);
- child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg);
- } else if (is_comptime) {
- return sema.failWithNeededComptime(block, arg_src);
+ if (is_comptime) {
+ if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| {
+ const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val);
+ child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg);
+ } else {
+ return sema.failWithNeededComptime(block, arg_src);
+ }
} else if (is_anytype) {
// We insert into the map an instruction which is runtime-known
- // but has the type of the comptime argument.
+ // but has the type of the argument.
const child_arg = try child_block.addArg(sema.typeOf(arg), 0);
child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg);
}
@@ -4558,6 +4533,19 @@ fn zirFieldPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src);
}
+fn zirFieldCallBind(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
+ const field_name = sema.code.nullTerminatedString(extra.field_name_start);
+ const object_ptr = sema.resolveInst(extra.lhs);
+ return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src);
+}
+
fn zirFieldValNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@@ -4584,6 +4572,19 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src);
}
+fn zirFieldCallBindNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src = inst_data.src();
+ const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
+ const object_ptr = sema.resolveInst(extra.lhs);
+ const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name);
+ return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src);
+}
+
fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@@ -9484,6 +9485,148 @@ fn fieldPtr(
return mod.fail(&block.base, src, "type '{}' does not support field access", .{object_ty});
}
+fn fieldCallBind(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ raw_ptr: Air.Inst.Ref,
+ field_name: []const u8,
+ field_name_src: LazySrcLoc,
+) CompileError!Air.Inst.Ref {
+ // When editing this function, note that there is corresponding logic to be edited
+ // in `fieldVal`. This function takes a pointer and returns a pointer.
+
+ const mod = sema.mod;
+ const raw_ptr_src = src; // TODO better source location
+ const raw_ptr_ty = sema.typeOf(raw_ptr);
+ const inner_ty = if (raw_ptr_ty.zigTypeTag() == .Pointer and raw_ptr_ty.ptrSize() == .One)
+ raw_ptr_ty.childType()
+ else
+ return mod.fail(&block.base, raw_ptr_src, "expected single pointer, found '{}'", .{raw_ptr_ty});
+
+ // Optionally dereference a second pointer to get the concrete type.
+ const is_double_ptr = inner_ty.zigTypeTag() == .Pointer and inner_ty.ptrSize() == .One;
+ const concrete_ty = if (is_double_ptr) inner_ty.childType() else inner_ty;
+ const ptr_ty = if (is_double_ptr) inner_ty else raw_ptr_ty;
+ const object_ptr = if (is_double_ptr)
+ try sema.analyzeLoad(block, src, raw_ptr, src)
+ else
+ raw_ptr;
+
+ const arena = sema.arena;
+ find_field: {
+ switch (concrete_ty.zigTypeTag()) {
+ .Struct => {
+ const struct_ty = try sema.resolveTypeFields(block, src, concrete_ty);
+ const struct_obj = struct_ty.castTag(.@"struct").?.data;
+
+ const field_index = struct_obj.fields.getIndex(field_name) orelse
+ break :find_field;
+ const field = struct_obj.fields.values()[field_index];
+
+ const ptr_field_ty = try Type.ptr(arena, .{
+ .pointee_type = field.ty,
+ .mutable = ptr_ty.ptrIsMutable(),
+ .@"addrspace" = ptr_ty.ptrAddressSpace(),
+ });
+
+ if (try sema.resolveDefinedValue(block, src, object_ptr)) |struct_ptr_val| {
+ const pointer = try sema.addConstant(
+ ptr_field_ty,
+ try Value.Tag.field_ptr.create(arena, .{
+ .container_ptr = struct_ptr_val,
+ .field_index = field_index,
+ }),
+ );
+ return sema.analyzeLoad(block, src, pointer, src);
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ const ptr_inst = ptr_inst: {
+ const tag: Air.Inst.Tag = switch (field_index) {
+ 0 => .struct_field_ptr_index_0,
+ 1 => .struct_field_ptr_index_1,
+ 2 => .struct_field_ptr_index_2,
+ 3 => .struct_field_ptr_index_3,
+ else => {
+ break :ptr_inst try block.addInst(.{
+ .tag = .struct_field_ptr,
+ .data = .{ .ty_pl = .{
+ .ty = try sema.addType(ptr_field_ty),
+ .payload = try sema.addExtra(Air.StructField{
+ .struct_operand = object_ptr,
+ .field_index = @intCast(u32, field_index),
+ }),
+ } },
+ });
+ },
+ };
+ break :ptr_inst try block.addInst(.{
+ .tag = tag,
+ .data = .{ .ty_op = .{
+ .ty = try sema.addType(ptr_field_ty),
+ .operand = object_ptr,
+ } },
+ });
+ };
+ return sema.analyzeLoad(block, src, ptr_inst, src);
+ },
+ .Union => return sema.mod.fail(&block.base, src, "TODO implement field calls on unions", .{}),
+ .Type => {
+ const namespace = try sema.analyzeLoad(block, src, object_ptr, src);
+ return sema.fieldVal(block, src, namespace, field_name, field_name_src);
+ },
+ else => {},
+ }
+ }
+
+ // If we get here, we need to look for a decl in the struct type instead.
+ switch (concrete_ty.zigTypeTag()) {
+ .Struct, .Opaque, .Union, .Enum => {
+ if (concrete_ty.getNamespace()) |namespace| {
+ if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| {
+ const decl_val = try sema.analyzeLoad(block, src, inst, src);
+ const decl_type = sema.typeOf(decl_val);
+ if (decl_type.zigTypeTag() == .Fn and
+ decl_type.fnParamLen() >= 1)
+ {
+ const first_param_type = decl_type.fnParamType(0);
+ const first_param_tag = first_param_type.tag();
+ // zig fmt: off
+ if (first_param_tag == .var_args_param or
+ first_param_tag == .generic_poison or (
+ first_param_type.zigTypeTag() == .Pointer and
+ first_param_type.ptrSize() == .One and
+ first_param_type.childType().eql(concrete_ty)))
+ {
+ // zig fmt: on
+ // TODO: bound fn calls on rvalues should probably
+ // generate a by-value argument somehow.
+ const ty = Type.Tag.bound_fn.init();
+ const value = try Value.Tag.bound_fn.create(arena, .{
+ .func_inst = decl_val,
+ .arg0_inst = object_ptr,
+ });
+ return sema.addConstant(ty, value);
+ } else if (first_param_type.eql(concrete_ty)) {
+ var deref = try sema.analyzeLoad(block, src, object_ptr, src);
+ const ty = Type.Tag.bound_fn.init();
+ const value = try Value.Tag.bound_fn.create(arena, .{
+ .func_inst = decl_val,
+ .arg0_inst = deref,
+ });
+ return sema.addConstant(ty, value);
+ }
+ }
+ }
+ }
+ },
+ else => {},
+ }
+
+ return mod.fail(&block.base, src, "type '{}' has no field or member function named '{s}'", .{ concrete_ty, field_name });
+}
+
fn namespaceLookup(
sema: *Sema,
block: *Scope.Block,
@@ -9850,14 +9993,14 @@ fn coerce(
if (dest_type.eql(inst_ty))
return inst;
- const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false);
+ const mod = sema.mod;
+ const arena = sema.arena;
+
+ const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false, mod.getTarget());
if (in_memory_result == .ok) {
return sema.bitcast(block, dest_type, inst, inst_src);
}
- const mod = sema.mod;
- const arena = sema.arena;
-
// undefined to anything
if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| {
if (val.isUndef() or inst_ty.zigTypeTag() == .Undefined) {
@@ -9898,7 +10041,7 @@ fn coerce(
if (inst_ty.ptrAddressSpace() != dest_type.ptrAddressSpace()) break :src_array_ptr;
const dst_elem_type = dest_type.elemType();
- switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut)) {
+ switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut, mod.getTarget())) {
.ok => {},
.no_match => break :src_array_ptr,
}
@@ -10024,7 +10167,7 @@ const InMemoryCoercionResult = enum {
/// * sentinel-terminated pointers can coerce into `[*]`
/// TODO improve this function to report recursive compile errors like it does in stage1.
/// look at the function types_match_const_cast_only
-fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InMemoryCoercionResult {
+fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult {
if (dest_type.eql(src_type))
return .ok;
@@ -10034,7 +10177,7 @@ fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InM
const dest_info = dest_type.ptrInfo().data;
const src_info = src_type.ptrInfo().data;
- const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable);
+ const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target);
if (child == .no_match) {
return child;
}
@@ -10081,11 +10224,19 @@ fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InM
return .no_match;
}
- assert(src_info.@"align" != 0);
- assert(dest_info.@"align" != 0);
+ // If both pointers have alignment 0, it means they both want ABI alignment.
+ // In this case, if they share the same child type, no need to resolve
+ // pointee type alignment. Otherwise both pointee types must have their alignment
+ // resolved and we compare the alignment numerically.
+ if (src_info.@"align" != 0 or dest_info.@"align" != 0 or
+ !dest_info.pointee_type.eql(src_info.pointee_type))
+ {
+ const src_align = src_type.ptrAlignment(target);
+ const dest_align = dest_type.ptrAlignment(target);
- if (dest_info.@"align" > src_info.@"align") {
- return .no_match;
+ if (dest_align > src_align) {
+ return .no_match;
+ }
}
return .ok;
@@ -11606,6 +11757,7 @@ fn typeHasOnePossibleValue(
.single_const_pointer,
.single_mut_pointer,
.pointer,
+ .bound_fn,
=> return null,
.@"struct" => {
diff --git a/src/Zir.zig b/src/Zir.zig
index 83c43fd7f2..483880c9b6 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -70,6 +70,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en
u32 => code.extra[i],
Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]),
i32 => @bitCast(i32, code.extra[i]),
+ Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]),
else => @compileError("bad field type"),
};
i += 1;
@@ -222,17 +223,9 @@ pub const Inst = struct {
break_inline,
/// Uses the `node` union field.
breakpoint,
- /// Function call with modifier `.auto`.
+ /// Function call.
/// Uses `pl_node`. AST node is the function call. Payload is `Call`.
call,
- /// Same as `call` but it also does `ensure_result_used` on the return value.
- call_chkused,
- /// Same as `call` but with modifier `.compile_time`.
- call_compile_time,
- /// Same as `call` but with modifier `.no_suspend`.
- call_nosuspend,
- /// Same as `call` but with modifier `.async_kw`.
- call_async,
/// `<`
/// Uses the `pl_node` union field. Payload is `Bin`.
cmp_lt,
@@ -327,6 +320,15 @@ pub const Inst = struct {
/// This instruction also accepts a pointer.
/// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field.
field_val,
+ /// Given a pointer to a struct or object that contains virtual fields, returns the
+ /// named field. If there is no named field, searches in the type for a decl that
+ /// matches the field name. The decl is resolved and we ensure that it's a function
+ /// which can accept the object as the first parameter, with one pointer fixup. If
+ /// all of that works, this instruction produces a special "bound function" value
+ /// which contains both the function and the saved first parameter value.
+ /// Bound functions may only be used as the function parameter to a `call` or
+ /// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler.
+ field_call_bind,
/// Given a pointer to a struct or object that contains virtual fields, returns a pointer
/// to the named field. The field name is a comptime instruction. Used by @field.
/// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed.
@@ -335,6 +337,15 @@ pub const Inst = struct {
/// The field name is a comptime instruction. Used by @field.
/// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed.
field_val_named,
+ /// Given a pointer to a struct or object that contains virtual fields, returns the
+ /// named field. If there is no named field, searches in the type for a decl that
+ /// matches the field name. The decl is resolved and we ensure that it's a function
+ /// which can accept the object as the first parameter, with one pointer fixup. If
+ /// all of that works, this instruction produces a special "bound function" value
+ /// which contains both the function and the saved first parameter value.
+ /// Bound functions may only be used as the function parameter to a `call` or
+ /// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler.
+ field_call_bind_named,
/// Returns a function type, or a function instance, depending on whether
/// the body_len is 0. Calling convention is auto.
/// Uses the `pl_node` union field. `payload_index` points to a `Func`.
@@ -395,14 +406,6 @@ pub const Inst = struct {
/// Twos complement wrapping integer multiplication.
/// Uses the `pl_node` union field. Payload is `Bin`.
mulwrap,
- /// Given a reference to a function and a parameter index, returns the
- /// type of the parameter. The only usage of this instruction is for the
- /// result location of parameters of function calls. In the case of a function's
- /// parameter type being `anytype`, it is the type coercion's job to detect this
- /// scenario and skip the coercion, so that semantic analysis of this instruction
- /// is not in a position where it must create an invalid type.
- /// Uses the `param_type` union field.
- param_type,
/// 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,
@@ -988,10 +991,6 @@ pub const Inst = struct {
.breakpoint,
.fence,
.call,
- .call_chkused,
- .call_compile_time,
- .call_nosuspend,
- .call_async,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -1017,8 +1016,10 @@ pub const Inst = struct {
.export_value,
.field_ptr,
.field_val,
+ .field_call_bind,
.field_ptr_named,
.field_val_named,
+ .field_call_bind_named,
.func,
.func_inferred,
.has_decl,
@@ -1034,7 +1035,6 @@ pub const Inst = struct {
.mod_rem,
.mul,
.mulwrap,
- .param_type,
.ref,
.shl,
.shr,
@@ -1247,10 +1247,6 @@ pub const Inst = struct {
.break_inline = .@"break",
.breakpoint = .node,
.call = .pl_node,
- .call_chkused = .pl_node,
- .call_compile_time = .pl_node,
- .call_nosuspend = .pl_node,
- .call_async = .pl_node,
.cmp_lt = .pl_node,
.cmp_lte = .pl_node,
.cmp_eq = .pl_node,
@@ -1282,6 +1278,8 @@ pub const Inst = struct {
.field_val = .pl_node,
.field_ptr_named = .pl_node,
.field_val_named = .pl_node,
+ .field_call_bind = .pl_node,
+ .field_call_bind_named = .pl_node,
.func = .pl_node,
.func_inferred = .pl_node,
.import = .str_tok,
@@ -1301,7 +1299,6 @@ pub const Inst = struct {
.mod_rem = .pl_node,
.mul = .pl_node,
.mulwrap = .pl_node,
- .param_type = .param_type,
.ref = .un_tok,
.ret_node = .un_node,
.ret_load = .un_node,
@@ -2170,10 +2167,6 @@ pub const Inst = struct {
/// Points to a `Block`.
payload_index: u32,
},
- param_type: struct {
- callee: Ref,
- param_index: u32,
- },
@"unreachable": struct {
/// Offset from Decl AST node index.
/// `Tag` determines which kind of AST node this points to.
@@ -2244,7 +2237,6 @@ pub const Inst = struct {
ptr_type,
int_type,
bool_br,
- param_type,
@"unreachable",
@"break",
switch_capture,
@@ -2372,8 +2364,27 @@ pub const Inst = struct {
/// Stored inside extra, with trailing arguments according to `args_len`.
/// Each argument is a `Ref`.
pub const Call = struct {
+ // Note: Flags *must* come first so that unusedResultExpr
+ // can find it when it goes to modify them.
+ flags: Flags,
callee: Ref,
- args_len: u32,
+
+ pub const Flags = packed struct {
+ /// std.builtin.CallOptions.Modifier in packed form
+ pub const PackedModifier = u3;
+ pub const PackedArgsLen = u28;
+
+ packed_modifier: PackedModifier,
+ ensure_result_used: bool = false,
+ args_len: PackedArgsLen,
+
+ comptime {
+ if (@sizeOf(Flags) != 4 or @bitSizeOf(Flags) != 32)
+ @compileError("Layout of Call.Flags needs to be updated!");
+ if (@bitSizeOf(std.builtin.CallOptions.Modifier) != @bitSizeOf(PackedModifier))
+ @compileError("Call.Flags.PackedModifier needs to be updated!");
+ }
+ };
};
pub const BuiltinCall = struct {
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 6ae218ed22..3834a694e9 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -179,7 +179,6 @@ const Writer = struct {
=> try self.writeBoolBr(stream, inst),
.array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst),
- .param_type => try self.writeParamType(stream, inst),
.ptr_type_simple => try self.writePtrTypeSimple(stream, inst),
.ptr_type => try self.writePtrType(stream, inst),
.int => try self.writeInt(stream, inst),
@@ -195,8 +194,6 @@ const Writer = struct {
.elem_ptr_node,
.elem_val_node,
- .field_ptr_named,
- .field_val_named,
.slice_start,
.slice_end,
.slice_sentinel,
@@ -288,12 +285,7 @@ const Writer = struct {
.@"export" => try self.writePlNodeExport(stream, inst),
.export_value => try self.writePlNodeExportValue(stream, inst),
- .call,
- .call_chkused,
- .call_compile_time,
- .call_nosuspend,
- .call_async,
- => try self.writePlNodeCall(stream, inst),
+ .call => try self.writePlNodeCall(stream, inst),
.block,
.block_inline,
@@ -328,8 +320,14 @@ const Writer = struct {
.field_ptr,
.field_val,
+ .field_call_bind,
=> try self.writePlNodeField(stream, inst),
+ .field_ptr_named,
+ .field_val_named,
+ .field_call_bind_named,
+ => try self.writePlNodeFieldNamed(stream, inst),
+
.as_node => try self.writeAs(stream, inst),
.breakpoint,
@@ -481,16 +479,6 @@ const Writer = struct {
try stream.writeAll("TODO)");
}
- fn writeParamType(
- self: *Writer,
- stream: anytype,
- inst: Zir.Inst.Index,
- ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
- const inst_data = self.code.instructions.items(.data)[inst].param_type;
- try self.writeInstRef(stream, inst_data.callee);
- try stream.print(", {d})", .{inst_data.param_index});
- }
-
fn writePtrTypeSimple(
self: *Writer,
stream: anytype,
@@ -881,8 +869,12 @@ const Writer = struct {
fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);
- const args = self.code.refSlice(extra.end, extra.data.args_len);
+ const args = self.code.refSlice(extra.end, extra.data.flags.args_len);
+ if (extra.data.flags.ensure_result_used) {
+ try stream.writeAll("nodiscard ");
+ }
+ try stream.print(".{s}, ", .{@tagName(@intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier))});
try self.writeInstRef(stream, extra.data.callee);
try stream.writeAll(", [");
for (args) |arg, i| {
@@ -1637,6 +1629,16 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
+ fn writePlNodeFieldNamed(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
+ try self.writeInstRef(stream, extra.lhs);
+ try stream.writeAll(", ");
+ try self.writeInstRef(stream, extra.field_name);
+ try stream.writeAll(") ");
+ try self.writeSrc(stream, inst_data.src());
+ }
+
fn writeAs(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.As, inst_data.payload_index).data;
diff --git a/src/type.zig b/src/type.zig
index 781fe74d45..e13ede852a 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -138,6 +138,7 @@ pub const Type = extern union {
.type_info,
=> return .Union,
+ .bound_fn => unreachable,
.var_args_param => unreachable, // can be any type
}
}
@@ -771,6 +772,7 @@ pub const Type = extern union {
.type_info,
.@"anyframe",
.generic_poison,
+ .bound_fn,
=> unreachable,
.array_u8,
@@ -936,6 +938,7 @@ pub const Type = extern union {
.comptime_float,
.noreturn,
.var_args_param,
+ .bound_fn,
=> return writer.writeAll(@tagName(t)),
.enum_literal => return writer.writeAll("@Type(.EnumLiteral)"),
@@ -1248,6 +1251,7 @@ pub const Type = extern union {
.var_args_param => unreachable,
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
+ .bound_fn => unreachable,
.array_u8,
.array_u8_sentinel_0,
@@ -1479,6 +1483,7 @@ pub const Type = extern union {
.empty_struct_literal,
.@"opaque",
.type_info,
+ .bound_fn,
=> false,
.inferred_alloc_const => unreachable,
@@ -1489,7 +1494,9 @@ pub const Type = extern union {
}
pub fn isNoReturn(self: Type) bool {
- const definitely_correct_result = self.zigTypeTag() == .NoReturn;
+ const definitely_correct_result =
+ self.tag_if_small_enough != .bound_fn and
+ self.zigTypeTag() == .NoReturn;
const fast_result = self.tag_if_small_enough == Tag.noreturn;
assert(fast_result == definitely_correct_result);
return fast_result;
@@ -1736,6 +1743,7 @@ pub const Type = extern union {
.@"opaque",
.var_args_param,
.type_info,
+ .bound_fn,
=> unreachable,
.generic_poison => unreachable,
@@ -1768,6 +1776,7 @@ pub const Type = extern union {
.var_args_param => unreachable,
.generic_poison => unreachable,
.type_info => unreachable,
+ .bound_fn => unreachable,
.@"struct" => {
const s = self.castTag(.@"struct").?.data;
@@ -1951,6 +1960,7 @@ pub const Type = extern union {
.@"opaque" => unreachable,
.var_args_param => unreachable,
.generic_poison => unreachable,
+ .bound_fn => unreachable,
.@"struct" => {
@panic("TODO bitSize struct");
@@ -2353,6 +2363,51 @@ pub const Type = extern union {
}
}
+ /// Returns if type can be used for a runtime variable
+ pub fn isValidVarType(self: Type, is_extern: bool) bool {
+ var ty = self;
+ while (true) switch (ty.zigTypeTag()) {
+ .Bool,
+ .Int,
+ .Float,
+ .ErrorSet,
+ .Enum,
+ .Frame,
+ .AnyFrame,
+ => return true,
+
+ .Opaque => return is_extern,
+ .BoundFn,
+ .ComptimeFloat,
+ .ComptimeInt,
+ .EnumLiteral,
+ .NoReturn,
+ .Type,
+ .Void,
+ .Undefined,
+ .Null,
+ => return false,
+
+ .Optional => {
+ var buf: Payload.ElemType = undefined;
+ return ty.optionalChild(&buf).isValidVarType(is_extern);
+ },
+ .Pointer, .Array, .Vector => ty = ty.elemType(),
+ .ErrorUnion => ty = ty.errorUnionPayload(),
+
+ .Fn => @panic("TODO fn isValidVarType"),
+ .Struct => {
+ // TODO this is not always correct; introduce lazy value mechanism
+ // and here we need to force a resolve of "type requires comptime".
+ return true;
+ },
+ .Union => @panic("TODO union isValidVarType"),
+ };
+ }
+
+ /// For *[N]T, returns [N]T.
+ /// For *T, returns T.
+ /// For [*]T, returns T.
pub fn childType(ty: Type) Type {
return switch (ty.tag()) {
.vector => ty.castTag(.vector).?.data.elem_type,
@@ -2934,6 +2989,7 @@ pub const Type = extern union {
.single_const_pointer,
.single_mut_pointer,
.pointer,
+ .bound_fn,
=> return null,
.@"struct" => {
@@ -3480,6 +3536,7 @@ pub const Type = extern union {
inferred_alloc_mut,
/// Same as `inferred_alloc_mut` but the local is `var` not `const`.
inferred_alloc_const, // See last_no_payload_tag below.
+ bound_fn,
// After this, the tag requires a payload.
array_u8,
@@ -3518,7 +3575,7 @@ pub const Type = extern union {
enum_full,
enum_nonexhaustive,
- pub const last_no_payload_tag = Tag.inferred_alloc_const;
+ pub const last_no_payload_tag = Tag.bound_fn;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
pub fn Type(comptime t: Tag) type {
@@ -3585,6 +3642,7 @@ pub const Type = extern union {
.extern_options,
.type_info,
.@"anyframe",
+ .bound_fn,
=> @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
.array_u8,
diff --git a/src/value.zig b/src/value.zig
index 69f8945e01..336f5f9cf7 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -159,6 +159,10 @@ pub const Value = extern union {
/// Used to coordinate alloc_inferred, store_to_inferred_ptr, and resolve_inferred_alloc
/// instructions for comptime code.
inferred_alloc_comptime,
+ /// Used sometimes as the result of field_call_bind. This value is always temporary,
+ /// and refers directly to the air. It will never be referenced by the air itself.
+ /// TODO: This is probably a bad encoding, maybe put temp data in the sema instead.
+ bound_fn,
pub const last_no_payload_tag = Tag.empty_array;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@@ -279,6 +283,7 @@ pub const Value = extern union {
.inferred_alloc => Payload.InferredAlloc,
.@"struct" => Payload.Struct,
.@"union" => Payload.Union,
+ .bound_fn => Payload.BoundFn,
};
}
@@ -422,6 +427,7 @@ pub const Value = extern union {
.extern_options_type,
.type_info_type,
.generic_poison,
+ .bound_fn,
=> unreachable,
.ty => {
@@ -716,6 +722,10 @@ pub const Value = extern union {
try out_stream.writeAll("(opt_payload_ptr)");
val = val.castTag(.opt_payload_ptr).?.data;
},
+ .bound_fn => {
+ const bound_func = val.castTag(.bound_fn).?.data;
+ return out_stream.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst });
+ },
};
}
@@ -2199,6 +2209,16 @@ pub const Value = extern union {
val: Value,
},
};
+
+ pub const BoundFn = struct {
+ pub const base_tag = Tag.bound_fn;
+
+ base: Payload = Payload{ .tag = base_tag },
+ data: struct {
+ func_inst: Air.Inst.Ref,
+ arg0_inst: Air.Inst.Ref,
+ },
+ };
};
/// Big enough to fit any non-BigInt value
diff --git a/test/behavior.zig b/test/behavior.zig
index 3a5c0fe589..4bfd947fcf 100644
--- a/test/behavior.zig
+++ b/test/behavior.zig
@@ -10,6 +10,7 @@ test {
_ = @import("behavior/eval.zig");
_ = @import("behavior/generics.zig");
_ = @import("behavior/if.zig");
+ _ = @import("behavior/member_func.zig");
_ = @import("behavior/pointers.zig");
_ = @import("behavior/sizeof_and_typeof.zig");
_ = @import("behavior/struct.zig");
diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig
index 0d103bc49a..6acdf15e89 100644
--- a/test/behavior/eval.zig
+++ b/test/behavior/eval.zig
@@ -155,3 +155,31 @@ fn MakeType(comptime T: type) type {
field: T,
};
}
+
+test "try to trick eval with runtime if" {
+ try expect(testTryToTrickEvalWithRuntimeIf(true) == 10);
+}
+
+fn testTryToTrickEvalWithRuntimeIf(b: bool) usize {
+ comptime var i: usize = 0;
+ inline while (i < 10) : (i += 1) {
+ const result = if (b) false else true;
+ _ = result;
+ }
+ comptime {
+ return i;
+ }
+}
+
+test "@setEvalBranchQuota" {
+ comptime {
+ // 1001 for the loop and then 1 more for the expect fn call
+ @setEvalBranchQuota(1002);
+ var i = 0;
+ var sum = 0;
+ while (i < 1001) : (i += 1) {
+ sum += i;
+ }
+ try expect(sum == 500500);
+ }
+}
diff --git a/test/behavior/eval_stage1.zig b/test/behavior/eval_stage1.zig
index 743f7af69e..4e945d7af0 100644
--- a/test/behavior/eval_stage1.zig
+++ b/test/behavior/eval_stage1.zig
@@ -109,21 +109,6 @@ test "const slice" {
}
}
-test "try to trick eval with runtime if" {
- try expect(testTryToTrickEvalWithRuntimeIf(true) == 10);
-}
-
-fn testTryToTrickEvalWithRuntimeIf(b: bool) usize {
- comptime var i: usize = 0;
- inline while (i < 10) : (i += 1) {
- const result = if (b) false else true;
- _ = result;
- }
- comptime {
- return i;
- }
-}
-
test "inlined loop has array literal with elided runtime scope on first iteration but not second iteration" {
var runtime = [1]i32{3};
comptime var i: usize = 0;
@@ -276,19 +261,6 @@ fn assertEqualPtrs(ptr1: *const u8, ptr2: *const u8) !void {
try expect(ptr1 == ptr2);
}
-test "@setEvalBranchQuota" {
- comptime {
- // 1001 for the loop and then 1 more for the expect fn call
- @setEvalBranchQuota(1002);
- var i = 0;
- var sum = 0;
- while (i < 1001) : (i += 1) {
- sum += i;
- }
- try expect(sum == 500500);
- }
-}
-
test "float literal at compile time not lossy" {
try expect(16777216.0 + 1.0 == 16777217.0);
try expect(9007199254740992.0 + 1.0 == 9007199254740993.0);
diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig
index f642028e54..76ad5cede7 100644
--- a/test/behavior/generics.zig
+++ b/test/behavior/generics.zig
@@ -118,3 +118,19 @@ pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type {
prealloc_items: [STATIC_SIZE]T,
};
}
+
+test "const decls in struct" {
+ try expect(GenericDataThing(3).count_plus_one == 4);
+}
+fn GenericDataThing(comptime count: isize) type {
+ return struct {
+ const count_plus_one = count + 1;
+ };
+}
+
+test "use generic param in generic param" {
+ try expect(aGenericFn(i32, 3, 4) == 7);
+}
+fn aGenericFn(comptime T: type, comptime a: T, b: T) T {
+ return a + b;
+}
diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig
index 4fa52f5377..c4b9687aa6 100644
--- a/test/behavior/generics_stage1.zig
+++ b/test/behavior/generics_stage1.zig
@@ -26,22 +26,6 @@ fn GenNode(comptime T: type) type {
};
}
-test "const decls in struct" {
- try expect(GenericDataThing(3).count_plus_one == 4);
-}
-fn GenericDataThing(comptime count: isize) type {
- return struct {
- const count_plus_one = count + 1;
- };
-}
-
-test "use generic param in generic param" {
- try expect(aGenericFn(i32, 3, 4) == 7);
-}
-fn aGenericFn(comptime T: type, comptime a: T, b: T) T {
- return a + b;
-}
-
test "generic fn with implicit cast" {
try expect(getFirstByte(u8, &[_]u8{13}) == 13);
try expect(getFirstByte(u16, &[_]u16{
diff --git a/test/behavior/member_func.zig b/test/behavior/member_func.zig
new file mode 100644
index 0000000000..092a691901
--- /dev/null
+++ b/test/behavior/member_func.zig
@@ -0,0 +1,103 @@
+const expect = @import("std").testing.expect;
+
+const HasFuncs = struct {
+ state: u32,
+ func_field: fn (u32) u32,
+
+ fn inc(self: *HasFuncs) void {
+ self.state += 1;
+ }
+
+ fn get(self: HasFuncs) u32 {
+ return self.state;
+ }
+
+ fn getPtr(self: *const HasFuncs) *const u32 {
+ return &self.state;
+ }
+
+ fn one(_: u32) u32 {
+ return 1;
+ }
+ fn two(_: u32) u32 {
+ return 2;
+ }
+};
+
+test "standard field calls" {
+ try expect(HasFuncs.one(0) == 1);
+ try expect(HasFuncs.two(0) == 2);
+
+ var v: HasFuncs = undefined;
+ v.state = 0;
+ v.func_field = HasFuncs.one;
+
+ const pv = &v;
+ const pcv: *const HasFuncs = pv;
+
+ try expect(v.get() == 0);
+ v.inc();
+ try expect(v.state == 1);
+ try expect(v.get() == 1);
+
+ pv.inc();
+ try expect(v.state == 2);
+ try expect(pv.get() == 2);
+ try expect(v.getPtr().* == 2);
+ try expect(pcv.get() == 2);
+ try expect(pcv.getPtr().* == 2);
+
+ v.func_field = HasFuncs.one;
+ try expect(v.func_field(0) == 1);
+ try expect(pv.func_field(0) == 1);
+ try expect(pcv.func_field(0) == 1);
+
+ try expect(pcv.func_field(blk: {
+ pv.func_field = HasFuncs.two;
+ break :blk 0;
+ }) == 1);
+
+ v.func_field = HasFuncs.two;
+ try expect(v.func_field(0) == 2);
+ try expect(pv.func_field(0) == 2);
+ try expect(pcv.func_field(0) == 2);
+}
+
+test "@field field calls" {
+ try expect(@field(HasFuncs, "one")(0) == 1);
+ try expect(@field(HasFuncs, "two")(0) == 2);
+
+ var v: HasFuncs = undefined;
+ v.state = 0;
+ v.func_field = HasFuncs.one;
+
+ const pv = &v;
+ const pcv: *const HasFuncs = pv;
+
+ try expect(@field(v, "get")() == 0);
+ @field(v, "inc")();
+ try expect(v.state == 1);
+ try expect(@field(v, "get")() == 1);
+
+ @field(pv, "inc")();
+ try expect(v.state == 2);
+ try expect(@field(pv, "get")() == 2);
+ try expect(@field(v, "getPtr")().* == 2);
+ try expect(@field(pcv, "get")() == 2);
+ try expect(@field(pcv, "getPtr")().* == 2);
+
+ v.func_field = HasFuncs.one;
+ try expect(@field(v, "func_field")(0) == 1);
+ try expect(@field(pv, "func_field")(0) == 1);
+ try expect(@field(pcv, "func_field")(0) == 1);
+
+ try expect(@field(pcv, "func_field")(blk: {
+ pv.func_field = HasFuncs.two;
+ break :blk 0;
+ }) == 1);
+
+ v.func_field = HasFuncs.two;
+ try expect(@field(v, "func_field")(0) == 2);
+ try expect(@field(pv, "func_field")(0) == 2);
+ try expect(@field(pcv, "func_field")(0) == 2);
+}
From 754ea118bc7192b75c337ae95ad54dbe619dea56 Mon Sep 17 00:00:00 2001
From: rgreenblatt
Date: Sat, 26 Jun 2021 11:28:03 -0500
Subject: [PATCH 126/160] improve panic hierarchy by always using builtin.panic
---
lib/std/builtin.zig | 2 +-
lib/std/debug.zig | 46 +++++++++++++++++++++++++++++++++++++--------
2 files changed, 39 insertions(+), 9 deletions(-)
diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig
index f01e7605ab..c0d2e9f725 100644
--- a/lib/std/builtin.zig
+++ b/lib/std/builtin.zig
@@ -710,7 +710,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn
},
else => {
const first_trace_addr = @returnAddress();
- std.debug.panicExtra(error_return_trace, first_trace_addr, "{s}", .{msg});
+ std.debug.panicImpl(error_return_trace, first_trace_addr, msg);
},
}
}
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
index 6e2cef1f0c..2194886c0a 100644
--- a/lib/std/debug.zig
+++ b/lib/std/debug.zig
@@ -228,9 +228,32 @@ pub fn assert(ok: bool) void {
pub fn panic(comptime format: []const u8, args: anytype) noreturn {
@setCold(true);
- // TODO: remove conditional once wasi / LLVM defines __builtin_return_address
- const first_trace_addr = if (native_os == .wasi) null else @returnAddress();
- panicExtra(null, first_trace_addr, format, args);
+
+ panicExtra(null, format, args);
+}
+
+/// `panicExtra` is useful when you want to print out an `@errorReturnTrace`
+/// and also print out some values.
+pub fn panicExtra(
+ trace: ?*builtin.StackTrace,
+ comptime format: []const u8,
+ args: anytype,
+) noreturn {
+ @setCold(true);
+
+ const size = 0x1000;
+ const trunc_msg = "(msg truncated)";
+ var buf: [size + trunc_msg.len]u8 = undefined;
+ // a minor annoyance with this is that it will result in the NoSpaceLeft
+ // error being part of the @panic stack trace (but that error should
+ // only happen rarely)
+ const msg = std.fmt.bufPrint(buf[0..size], format, args) catch |err| switch (err) {
+ std.fmt.BufPrintError.NoSpaceLeft => blk: {
+ std.mem.copy(u8, buf[size..], trunc_msg);
+ break :blk &buf;
+ },
+ };
+ builtin.panic(msg, trace);
}
/// Non-zero whenever the program triggered a panic.
@@ -244,7 +267,9 @@ var panic_mutex = std.Thread.Mutex{};
/// This is used to catch and handle panics triggered by the panic handler.
threadlocal var panic_stage: usize = 0;
-pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: anytype) noreturn {
+// `panicImpl` could be useful in implementing a custom panic handler which
+// calls the default handler (on supported platforms)
+pub fn panicImpl(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, msg: []const u8) noreturn {
@setCold(true);
if (enable_segfault_handler) {
@@ -271,7 +296,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
const current_thread_id = std.Thread.getCurrentId();
stderr.print("thread {} panic: ", .{current_thread_id}) catch os.abort();
}
- stderr.print(format ++ "\n", args) catch os.abort();
+ stderr.print("{s}\n", .{msg}) catch os.abort();
if (trace) |t| {
dumpStackTrace(t.*);
}
@@ -1626,9 +1651,14 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u
os.abort();
} else {
switch (msg) {
- 0 => panicExtra(null, exception_address, format.?, .{}),
- 1 => panicExtra(null, exception_address, "Segmentation fault at address 0x{x}", .{info.ExceptionRecord.ExceptionInformation[1]}),
- 2 => panicExtra(null, exception_address, "Illegal Instruction", .{}),
+ 0 => panicImpl(null, exception_address, format.?),
+ 1 => {
+ const format_item = "Segmentation fault at address 0x{x}";
+ var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large
+ const to_print = std.fmt.bufPrint(buf[0..buf.len], format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable;
+ panicImpl(null, exception_address, to_print);
+ },
+ 2 => panicImpl(null, exception_address, "Illegal Instruction"),
else => unreachable,
}
}
From 1e805df81d51a338eefaff0535e5f1cca9e22028 Mon Sep 17 00:00:00 2001
From: Matthew Borkowski
Date: Mon, 27 Sep 2021 14:06:14 -0400
Subject: [PATCH 127/160] deflate.zig: fix bits_left overflow at EndOfStream
and @intCast truncation with empty Huffman table
---
lib/std/compress/deflate.zig | 38 +++++++++++++++++++++++++++---------
1 file changed, 29 insertions(+), 9 deletions(-)
diff --git a/lib/std/compress/deflate.zig b/lib/std/compress/deflate.zig
index b443c2971f..2fef0b32bc 100644
--- a/lib/std/compress/deflate.zig
+++ b/lib/std/compress/deflate.zig
@@ -58,8 +58,10 @@ const Huffman = struct {
}
// All zero.
- if (self.count[0] == code_length.len)
+ if (self.count[0] == code_length.len) {
+ self.min_code_len = 0;
return;
+ }
var left: isize = 1;
for (self.count[1..]) |val| {
@@ -280,7 +282,7 @@ pub fn InflateStream(comptime ReaderType: type) type {
return self.bits & mask;
}
fn readBits(self: *Self, bits: usize) !u32 {
- const val = self.peekBits(bits);
+ const val = try self.peekBits(bits);
self.discardBits(bits);
return val;
}
@@ -487,6 +489,8 @@ pub fn InflateStream(comptime ReaderType: type) type {
// We can't read PREFIX_LUT_BITS as we don't want to read past the
// deflate stream end, use an incremental approach instead.
var code_len = h.min_code_len;
+ if (code_len == 0)
+ return error.OutOfCodes;
while (true) {
_ = try self.peekBits(code_len);
// Small optimization win, use as many bits as possible in the
@@ -658,11 +662,27 @@ test "lengths overflow" {
// f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6
// 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11
const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 };
-
- const reader = std.io.fixedBufferStream(&stream).reader();
- var window: [0x8000]u8 = undefined;
- var inflate = inflateStream(reader, &window);
-
- var buf: [1]u8 = undefined;
- try std.testing.expectError(error.InvalidLength, inflate.read(&buf));
+ try std.testing.expectError(error.InvalidLength, testInflate(stream[0..]));
+}
+
+test "empty distance alphabet" {
+ // dynamic block with empty distance alphabet is valid if end of data symbol is used immediately
+ // f dy hlit hdist hclen 16 17 18 0 8 7 9 6 10 5 11 4 12 3 13 2 14 1 15 (18) x128 (18) x128 (1) ( 0) (256)
+ // 1 10 00000 00000 1111 000 000 010 010 000 000 000 000 000 000 000 000 000 000 000 000 000 001 000 (11) 1110101 (11) 1110101 (0) (10) (0)
+ const stream = [_]u8{ 0b00000101, 0b11100000, 0b00000001, 0b00001001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b01011100, 0b10111111, 0b00101110 };
+ try testInflate(stream[0..]);
+}
+
+test "inflateStream fuzzing" {
+ // see https://github.com/ziglang/zig/issues/9842
+ try std.testing.expectError(error.EndOfStream, testInflate("\x950000"));
+ try std.testing.expectError(error.OutOfCodes, testInflate("\x950\x00\x0000000"));
+}
+
+fn testInflate(data: []const u8) !void {
+ var window: [0x8000]u8 = undefined;
+ const reader = std.io.fixedBufferStream(data).reader();
+ var inflate = inflateStream(reader, &window);
+ var inflated = try inflate.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize));
+ defer std.testing.allocator.free(inflated);
}
From 79bc5891c1c4cde0592fe1b10b6c9a85914155cf Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 15:45:58 -0700
Subject: [PATCH 128/160] stage2: more arithmetic support
* AIR: add `mod` instruction for modulus division
- Implement for LLVM backend
* Sema: implement `@mod`, `@rem`, and `%`.
* Sema: fix comptime switch evaluation
* Sema: implement comptime shift left
* Sema: fix the logic inside analyzeArithmetic to handle all the
nuances between the different mathematical operations.
- Implement comptime wrapping operations
---
src/Air.zig | 11 +-
src/Liveness.zig | 1 +
src/Sema.zig | 661 +++++++++++++++++++++-----
src/Zir.zig | 22 +-
src/codegen.zig | 9 +
src/codegen/c.zig | 2 +
src/codegen/llvm.zig | 29 ++
src/print_air.zig | 1 +
src/value.zig | 138 ++++++
test/behavior.zig | 3 +-
test/behavior/math.zig | 848 ---------------------------------
test/behavior/math_stage1.zig | 855 ++++++++++++++++++++++++++++++++++
12 files changed, 1605 insertions(+), 975 deletions(-)
create mode 100644 test/behavior/math_stage1.zig
diff --git a/src/Air.zig b/src/Air.zig
index b4552f9d7b..b5d19127a0 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -69,10 +69,16 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
div,
- /// Integer or float remainder.
- /// Both operands are guaranteed to be the same type, and the result type is the same as both operands.
+ /// Integer or float remainder division.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
/// Uses the `bin_op` field.
rem,
+ /// Integer or float modulus division.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ mod,
/// Add an offset to a pointer, returning a new pointer.
/// The offset is in element type units, not bytes.
/// Wrapping is undefined behavior.
@@ -568,6 +574,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.mulwrap,
.div,
.rem,
+ .mod,
.bit_and,
.bit_or,
.xor,
diff --git a/src/Liveness.zig b/src/Liveness.zig
index a9ff586aeb..25dd29b0f6 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -232,6 +232,7 @@ fn analyzeInst(
.mulwrap,
.div,
.rem,
+ .mod,
.ptr_add,
.ptr_sub,
.bit_and,
diff --git a/src/Sema.zig b/src/Sema.zig
index 35a434eb35..de94a8c6b8 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -319,8 +319,6 @@ pub fn analyzeBody(
.div_exact => try sema.zirDivExact(block, inst),
.div_floor => try sema.zirDivFloor(block, inst),
.div_trunc => try sema.zirDivTrunc(block, inst),
- .mod => try sema.zirMod(block, inst),
- .rem => try sema.zirRem(block, inst),
.shl_exact => try sema.zirShlExact(block, inst),
.shr_exact => try sema.zirShrExact(block, inst),
.bit_offset_of => try sema.zirBitOffsetOf(block, inst),
@@ -363,14 +361,16 @@ pub fn analyzeBody(
.error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon),
.error_set_decl_func => try sema.zirErrorSetDecl(block, inst, .func),
- .add => try sema.zirArithmetic(block, inst),
- .addwrap => try sema.zirArithmetic(block, inst),
- .div => try sema.zirArithmetic(block, inst),
- .mod_rem => try sema.zirArithmetic(block, inst),
- .mul => try sema.zirArithmetic(block, inst),
- .mulwrap => try sema.zirArithmetic(block, inst),
- .sub => try sema.zirArithmetic(block, inst),
- .subwrap => try sema.zirArithmetic(block, inst),
+ .add => try sema.zirArithmetic(block, inst, .add),
+ .addwrap => try sema.zirArithmetic(block, inst, .addwrap),
+ .div => try sema.zirArithmetic(block, inst, .div),
+ .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem),
+ .mod => try sema.zirArithmetic(block, inst, .mod),
+ .rem => try sema.zirArithmetic(block, inst, .rem),
+ .mul => try sema.zirArithmetic(block, inst, .mul),
+ .mulwrap => try sema.zirArithmetic(block, inst, .mulwrap),
+ .sub => try sema.zirArithmetic(block, inst, .sub),
+ .subwrap => try sema.zirArithmetic(block, inst, .subwrap),
// Instructions that we know to *always* be noreturn based solely on their tag.
// These functions match the return type of analyzeBody so that we can
@@ -886,6 +886,14 @@ fn failWithUseOfUndef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) Compile
return sema.mod.fail(&block.base, src, "use of undefined value here causes undefined behavior", .{});
}
+fn failWithDivideByZero(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) CompileError {
+ return sema.mod.fail(&block.base, src, "division by zero here causes undefined behavior", .{});
+}
+
+fn failWithModRemNegative(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, lhs_ty: Type, rhs_ty: Type) CompileError {
+ return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty });
+}
+
/// Appropriate to call when the coercion has already been done by result
/// location semantics. Asserts the value fits in the provided `Int` type.
/// Only supports `Int` types 64 bits or less.
@@ -2366,8 +2374,12 @@ fn resolveBlockBody(
body: []const Zir.Inst.Index,
merges: *Scope.Block.Merges,
) CompileError!Air.Inst.Ref {
- _ = try sema.analyzeBody(child_block, body);
- return sema.analyzeBlockBody(parent_block, src, child_block, merges);
+ if (child_block.is_comptime) {
+ return sema.resolveBody(child_block, body);
+ } else {
+ _ = try sema.analyzeBody(child_block, body);
+ return sema.analyzeBlockBody(parent_block, src, child_block, merges);
+ }
}
fn analyzeBlockBody(
@@ -5867,23 +5879,36 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
- const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| {
- if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
- if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return sema.addConstUndef(sema.typeOf(lhs));
- }
- return sema.mod.fail(&block.base, src, "TODO implement comptime shl", .{});
- }
- }
+ const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
+ const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
- try sema.requireRuntimeBlock(block, src);
+ const runtime_src = if (maybe_lhs_val) |lhs_val| rs: {
+ const lhs_ty = sema.typeOf(lhs);
+
+ if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty);
+ const rhs_val = maybe_rhs_val orelse break :rs rhs_src;
+ if (rhs_val.isUndef()) return sema.addConstUndef(lhs_ty);
+
+ // If rhs is 0, return lhs without doing any calculations.
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(lhs_ty, lhs_val);
+ }
+ const val = try lhs_val.shl(rhs_val, sema.arena);
+ return sema.addConstant(lhs_ty, val);
+ } else rs: {
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) return sema.addConstUndef(sema.typeOf(lhs));
+ }
+ break :rs lhs_src;
+ };
+
+ try sema.requireRuntimeBlock(block, runtime_src);
return block.addBinOp(.shl, lhs, rhs);
}
@@ -6141,11 +6166,15 @@ fn zirNegate(
return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
}
-fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirArithmetic(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: Zir.Inst.Index,
+ zir_tag: Zir.Inst.Tag,
+) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
- const tag_override = block.sema.code.instructions.items(.tag)[inst];
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
sema.src = .{ .node_offset_bin_op = inst_data.src_node };
const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
@@ -6154,7 +6183,7 @@ fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- return sema.analyzeArithmetic(block, tag_override, lhs, rhs, sema.src, lhs_src, rhs_src);
+ return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
}
fn zirOverflowArithmetic(
@@ -6187,6 +6216,7 @@ fn zirSatArithmetic(
fn analyzeArithmetic(
sema: *Sema,
block: *Scope.Block,
+ /// TODO performance investigation: make this comptime?
zir_tag: Zir.Inst.Tag,
lhs: Air.Inst.Ref,
rhs: Air.Inst.Ref,
@@ -6204,7 +6234,7 @@ fn analyzeArithmetic(
lhs_ty.arrayLen(), rhs_ty.arrayLen(),
});
}
- return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
+ return sema.mod.fail(&block.base, src, "TODO implement support for vectors in Sema.analyzeArithmetic", .{});
} else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
lhs_ty, rhs_ty,
@@ -6247,7 +6277,9 @@ fn analyzeArithmetic(
};
const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } });
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
+ .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
+ });
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
@@ -6267,86 +6299,499 @@ fn analyzeArithmetic(
});
}
- if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
- if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
- if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return sema.addConstUndef(resolved_type);
- }
- // incase rhs is 0, simply return lhs without doing any calculations
- // TODO Once division is implemented we should throw an error when dividing by 0.
- if (rhs_val.compareWithZero(.eq)) {
- switch (zir_tag) {
- .add, .addwrap, .sub, .subwrap => {
- return sema.addConstant(scalar_type, lhs_val);
- },
- else => {},
+ const target = sema.mod.getTarget();
+ const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs);
+ const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs);
+ const rs: struct { src: LazySrcLoc, air_tag: Air.Inst.Tag } = rs: {
+ switch (zir_tag) {
+ .add => {
+ // For integers:
+ // If either of the operands are zero, then the other operand is
+ // returned, even if it is undefined.
+ // If either of the operands are undefined, it's a compile error
+ // because there is a possible value for which the addition would
+ // overflow (max_int), causing illegal behavior.
+ // For floats: either operand being undef makes the result undef.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
+ return casted_rhs;
+ }
}
- }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ if (is_int) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ } else {
+ return sema.addConstUndef(scalar_type);
+ }
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ if (is_int) {
+ return sema.failWithUseOfUndef(block, lhs_src);
+ } else {
+ return sema.addConstUndef(scalar_type);
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.intAdd(rhs_val, sema.arena),
+ );
+ } else {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena),
+ );
+ }
+ } else break :rs .{ .src = rhs_src, .air_tag = .add };
+ } else break :rs .{ .src = lhs_src, .air_tag = .add };
+ },
+ .addwrap => {
+ // Integers only; floats are checked above.
+ // If either of the operands are zero, then the other operand is
+ // returned, even if it is undefined.
+ // If either of the operands are undefined, the result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
+ return casted_rhs;
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberAddWrap(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = lhs_src, .air_tag = .addwrap };
+ } else break :rs .{ .src = rhs_src, .air_tag = .addwrap };
+ },
+ .sub => {
+ // For integers:
+ // If the rhs is zero, then the other operand is
+ // returned, even if it is undefined.
+ // If either of the operands are undefined, it's a compile error
+ // because there is a possible value for which the subtraction would
+ // overflow, causing illegal behavior.
+ // For floats: either operand being undef makes the result undef.
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ if (is_int) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ } else {
+ return sema.addConstUndef(scalar_type);
+ }
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ if (is_int) {
+ return sema.failWithUseOfUndef(block, lhs_src);
+ } else {
+ return sema.addConstUndef(scalar_type);
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.intSub(rhs_val, sema.arena),
+ );
+ } else {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.floatSub(rhs_val, scalar_type, sema.arena),
+ );
+ }
+ } else break :rs .{ .src = rhs_src, .air_tag = .sub };
+ } else break :rs .{ .src = lhs_src, .air_tag = .sub };
+ },
+ .subwrap => {
+ // Integers only; floats are checked above.
+ // If the RHS is zero, then the other operand is returned, even if it is undefined.
+ // If either of the operands are undefined, the result is undefined.
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberSubWrap(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = rhs_src, .air_tag = .subwrap };
+ } else break :rs .{ .src = lhs_src, .air_tag = .subwrap };
+ },
+ .div => {
+ // For integers:
+ // If the lhs is zero, then zero is returned regardless of rhs.
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined:
+ // * if lhs type is signed:
+ // * if rhs is comptime-known and not -1, result is undefined
+ // * if rhs is -1 or runtime-known, compile error because there is a
+ // possible value (-min_int * -1) for which division would be
+ // illegal behavior.
+ // * if lhs type is unsigned, undef is returned regardless of rhs.
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (lhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ if (lhs_ty.isSignedInt() and rhs_ty.isSignedInt()) {
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.compare(.neq, Value.negative_one, scalar_type)) {
+ return sema.addConstUndef(scalar_type);
+ }
+ }
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ return sema.addConstUndef(scalar_type);
+ }
- const value = switch (zir_tag) {
- .add => blk: {
- const val = if (is_int)
- try lhs_val.intAdd(rhs_val, sema.arena)
- else
- try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena);
- break :blk val;
- },
- .sub => blk: {
- const val = if (is_int)
- try lhs_val.intSub(rhs_val, sema.arena)
- else
- try lhs_val.floatSub(rhs_val, scalar_type, sema.arena);
- break :blk val;
- },
- .div => blk: {
- const val = if (is_int)
- try lhs_val.intDiv(rhs_val, sema.arena)
- else
- try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena);
- break :blk val;
- },
- .mul => blk: {
- const val = if (is_int)
- try lhs_val.intMul(rhs_val, sema.arena)
- else
- try lhs_val.floatMul(rhs_val, scalar_type, sema.arena);
- break :blk val;
- },
- else => return sema.mod.fail(&block.base, src, "TODO implement comptime arithmetic for operand '{s}'", .{@tagName(zir_tag)}),
- };
-
- log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value });
-
- return sema.addConstant(scalar_type, value);
- } else {
- try sema.requireRuntimeBlock(block, rhs_src);
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.intDiv(rhs_val, sema.arena),
+ );
+ } else {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena),
+ );
+ }
+ } else break :rs .{ .src = rhs_src, .air_tag = .div };
+ } else break :rs .{ .src = lhs_src, .air_tag = .div };
+ },
+ .mul => {
+ // For integers:
+ // If either of the operands are zero, the result is zero.
+ // If either of the operands are one, the result is the other
+ // operand, even if it is undefined.
+ // If either of the operands are undefined, it's a compile error
+ // because there is a possible value for which the addition would
+ // overflow (max_int), causing illegal behavior.
+ // For floats: either operand being undef makes the result undef.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (lhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (lhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_rhs;
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ if (is_int) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ } else {
+ return sema.addConstUndef(scalar_type);
+ }
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (rhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_lhs;
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ if (is_int) {
+ return sema.failWithUseOfUndef(block, lhs_src);
+ } else {
+ return sema.addConstUndef(scalar_type);
+ }
+ }
+ if (is_int) {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.intMul(rhs_val, sema.arena),
+ );
+ } else {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.floatMul(rhs_val, scalar_type, sema.arena),
+ );
+ }
+ } else break :rs .{ .src = lhs_src, .air_tag = .mul };
+ } else break :rs .{ .src = rhs_src, .air_tag = .mul };
+ },
+ .mulwrap => {
+ // Integers only; floats are handled above.
+ // If either of the operands are zero, the result is zero.
+ // If either of the operands are one, the result is the other
+ // operand, even if it is undefined.
+ // If either of the operands are undefined, the result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (lhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (lhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_rhs;
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (rhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_lhs;
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberMulWrap(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap };
+ } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap };
+ },
+ .mod_rem => {
+ // For integers:
+ // Either operand being undef is a compile error because there exists
+ // a possible value (TODO what is it?) that would invoke illegal behavior.
+ // TODO: can lhs zero be handled better?
+ // TODO: can lhs undef be handled better?
+ //
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, result is undefined.
+ //
+ // For either one: if the result would be different between @mod and @rem,
+ // then emit a compile error saying you have to pick one.
+ if (is_int) {
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ if (lhs_val.compareWithZero(.lt)) {
+ return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+ }
+ } else if (lhs_ty.isSignedInt()) {
+ return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.lt)) {
+ return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.intRem(rhs_val, sema.arena),
+ );
+ }
+ break :rs .{ .src = lhs_src, .air_tag = .rem };
+ } else if (rhs_ty.isSignedInt()) {
+ return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+ } else {
+ break :rs .{ .src = rhs_src, .air_tag = .rem };
+ }
+ }
+ // float operands
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.lt)) {
+ return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef() or lhs_val.compareWithZero(.lt)) {
+ return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+ }
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.floatRem(rhs_val, sema.arena),
+ );
+ } else {
+ return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+ }
+ } else {
+ return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+ }
+ },
+ .rem => {
+ // For integers:
+ // Either operand being undef is a compile error because there exists
+ // a possible value (TODO what is it?) that would invoke illegal behavior.
+ // TODO: can lhs zero be handled better?
+ // TODO: can lhs undef be handled better?
+ //
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, result is undefined.
+ if (is_int) {
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.intRem(rhs_val, sema.arena),
+ );
+ }
+ break :rs .{ .src = lhs_src, .air_tag = .rem };
+ } else {
+ break :rs .{ .src = rhs_src, .air_tag = .rem };
+ }
+ }
+ // float operands
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.floatRem(rhs_val, sema.arena),
+ );
+ } else break :rs .{ .src = rhs_src, .air_tag = .rem };
+ } else break :rs .{ .src = lhs_src, .air_tag = .rem };
+ },
+ .mod => {
+ // For integers:
+ // Either operand being undef is a compile error because there exists
+ // a possible value (TODO what is it?) that would invoke illegal behavior.
+ // TODO: can lhs zero be handled better?
+ // TODO: can lhs undef be handled better?
+ //
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, result is undefined.
+ if (is_int) {
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.intMod(rhs_val, sema.arena),
+ );
+ }
+ break :rs .{ .src = lhs_src, .air_tag = .mod };
+ } else {
+ break :rs .{ .src = rhs_src, .air_tag = .mod };
+ }
+ }
+ // float operands
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.floatMod(rhs_val, sema.arena),
+ );
+ } else break :rs .{ .src = rhs_src, .air_tag = .mod };
+ } else break :rs .{ .src = lhs_src, .air_tag = .mod };
+ },
+ else => unreachable,
}
- } else {
- try sema.requireRuntimeBlock(block, lhs_src);
- }
-
- if (zir_tag == .mod_rem) {
- const dirty_lhs = lhs_ty.isSignedInt() or lhs_ty.isRuntimeFloat();
- const dirty_rhs = rhs_ty.isSignedInt() or rhs_ty.isRuntimeFloat();
- if (dirty_lhs or dirty_rhs) {
- return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty });
- }
- }
-
- const air_tag: Air.Inst.Tag = switch (zir_tag) {
- .add => .add,
- .addwrap => .addwrap,
- .sub => .sub,
- .subwrap => .subwrap,
- .mul => .mul,
- .mulwrap => .mulwrap,
- .div => .div,
- .mod_rem => .rem,
- .rem => .rem,
- else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for operand '{s}'", .{@tagName(zir_tag)}),
};
- return block.addBinOp(air_tag, casted_lhs, casted_rhs);
+ try sema.requireRuntimeBlock(block, rs.src);
+ return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs);
}
fn zirLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -7401,7 +7846,7 @@ fn analyzeRet(
fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
// extend this swich as additional operators are implemented
return switch (tag) {
- .add, .sub, .mul, .div => true,
+ .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true,
else => false,
};
}
@@ -8068,16 +8513,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{});
}
-fn zirMod(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
- const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirMod", .{});
-}
-
-fn zirRem(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- return sema.zirArithmetic(block, inst);
-}
-
fn zirShlExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
diff --git a/src/Zir.zig b/src/Zir.zig
index 483880c9b6..7c171e736d 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -395,17 +395,6 @@ pub const Inst = struct {
/// Merge two error sets into one, `E1 || E2`.
/// Uses the `pl_node` field with payload `Bin`.
merge_error_sets,
- /// Ambiguously remainder division or modulus. If the computation would possibly have
- /// a different value depending on whether the operation is remainder division or modulus,
- /// a compile error is emitted. Otherwise the computation is performed.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mod_rem,
- /// Arithmetic multiplication. Asserts no integer overflow.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mul,
- /// Twos complement wrapping integer multiplication.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mulwrap,
/// 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,
@@ -828,6 +817,17 @@ pub const Inst = struct {
/// Implements the `@rem` builtin.
/// Uses the `pl_node` union field with payload `Bin`.
rem,
+ /// 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.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mod_rem,
+ /// Arithmetic multiplication. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mul,
+ /// Twos complement wrapping integer multiplication.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mulwrap,
/// Integer shift-left. Zeroes are shifted in from the right hand side.
/// Uses the `pl_node` union field. Payload is `Bin`.
diff --git a/src/codegen.zig b/src/codegen.zig
index 4eda3f2594..7c359e90c0 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -832,6 +832,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.mulwrap => try self.airMulWrap(inst),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
+ .mod => try self.airMod(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -1353,6 +1354,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airMod(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement mod for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airBitAnd(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index a6534b1eba..4964f17cd3 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -897,6 +897,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
.rem => try airBinOp( f, inst, " % "),
+ // TODO implement modulus division
+ .mod => try airBinOp( f, inst, " mod "),
.cmp_eq => try airBinOp(f, inst, " == "),
.cmp_gt => try airBinOp(f, inst, " > "),
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 4a0d218ead..f65e0f6d72 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1244,6 +1244,7 @@ pub const FuncGen = struct {
.mulwrap => try self.airMul(inst, true),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
+ .mod => try self.airMod(inst),
.ptr_add => try self.airPtrAdd(inst),
.ptr_sub => try self.airPtrSub(inst),
@@ -2095,6 +2096,34 @@ pub const FuncGen = struct {
return self.builder.buildURem(lhs, rhs, "");
}
+ fn airMod(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.air.typeOfIndex(inst);
+ const inst_llvm_ty = try self.dg.llvmType(inst_ty);
+
+ if (inst_ty.isRuntimeFloat()) {
+ const a = self.builder.buildFRem(lhs, rhs, "");
+ const b = self.builder.buildFAdd(a, rhs, "");
+ const c = self.builder.buildFRem(b, rhs, "");
+ const zero = inst_llvm_ty.constNull();
+ const ltz = self.builder.buildFCmp(.OLT, lhs, zero, "");
+ return self.builder.buildSelect(ltz, c, a, "");
+ }
+ if (inst_ty.isSignedInt()) {
+ const a = self.builder.buildSRem(lhs, rhs, "");
+ const b = self.builder.buildNSWAdd(a, rhs, "");
+ const c = self.builder.buildSRem(b, rhs, "");
+ const zero = inst_llvm_ty.constNull();
+ const ltz = self.builder.buildICmp(.SLT, lhs, zero, "");
+ return self.builder.buildSelect(ltz, c, a, "");
+ }
+ return self.builder.buildURem(lhs, rhs, "");
+ }
+
fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
diff --git a/src/print_air.zig b/src/print_air.zig
index 2a7538f81a..90df06760b 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -110,6 +110,7 @@ const Writer = struct {
.mulwrap,
.div,
.rem,
+ .mod,
.ptr_add,
.ptr_sub,
.bit_and,
diff --git a/src/value.zig b/src/value.zig
index 336f5f9cf7..29d8fa8db9 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -1616,6 +1616,34 @@ pub const Value = extern union {
return result;
}
+ /// Supports both floats and ints; handles undefined.
+ pub fn numberMulWrap(
+ lhs: Value,
+ rhs: Value,
+ ty: Type,
+ arena: *Allocator,
+ target: Target,
+ ) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+ if (ty.isAnyFloat()) {
+ return floatMul(lhs, rhs, ty, arena);
+ }
+ const result = try intMul(lhs, rhs, arena);
+
+ const max = try ty.maxInt(arena, target);
+ if (compare(result, .gt, max, ty)) {
+ @panic("TODO comptime wrapping integer multiplication");
+ }
+
+ const min = try ty.minInt(arena, target);
+ if (compare(result, .lt, min, ty)) {
+ @panic("TODO comptime wrapping integer multiplication");
+ }
+
+ return result;
+ }
+
/// Supports both floats and ints; handles undefined.
pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value {
if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
@@ -1840,6 +1868,82 @@ pub const Value = extern union {
}
}
+ pub fn intRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+ // TODO is this a performance issue? maybe we should try the operation without
+ // resorting to BigInt first.
+ var lhs_space: Value.BigIntSpace = undefined;
+ var rhs_space: Value.BigIntSpace = undefined;
+ const lhs_bigint = lhs.toBigInt(&lhs_space);
+ const rhs_bigint = rhs.toBigInt(&rhs_space);
+ const limbs_q = try allocator.alloc(
+ std.math.big.Limb,
+ lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1,
+ );
+ const limbs_r = try allocator.alloc(
+ std.math.big.Limb,
+ lhs_bigint.limbs.len,
+ );
+ const limbs_buffer = try allocator.alloc(
+ std.math.big.Limb,
+ std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
+ );
+ var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined };
+ var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined };
+ result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null);
+ const result_limbs = result_r.limbs[0..result_r.len];
+
+ if (result_r.positive) {
+ return Value.Tag.int_big_positive.create(allocator, result_limbs);
+ } else {
+ return Value.Tag.int_big_negative.create(allocator, result_limbs);
+ }
+ }
+
+ pub fn intMod(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+ // TODO is this a performance issue? maybe we should try the operation without
+ // resorting to BigInt first.
+ var lhs_space: Value.BigIntSpace = undefined;
+ var rhs_space: Value.BigIntSpace = undefined;
+ const lhs_bigint = lhs.toBigInt(&lhs_space);
+ const rhs_bigint = rhs.toBigInt(&rhs_space);
+ const limbs_q = try allocator.alloc(
+ std.math.big.Limb,
+ lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1,
+ );
+ const limbs_r = try allocator.alloc(
+ std.math.big.Limb,
+ lhs_bigint.limbs.len,
+ );
+ const limbs_buffer = try allocator.alloc(
+ std.math.big.Limb,
+ std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
+ );
+ var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined };
+ var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined };
+ result_q.divFloor(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null);
+ const result_limbs = result_r.limbs[0..result_r.len];
+
+ if (result_r.positive) {
+ return Value.Tag.int_big_positive.create(allocator, result_limbs);
+ } else {
+ return Value.Tag.int_big_negative.create(allocator, result_limbs);
+ }
+ }
+
+ pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+ _ = lhs;
+ _ = rhs;
+ _ = allocator;
+ @panic("TODO implement Value.floatRem");
+ }
+
+ pub fn floatMod(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+ _ = lhs;
+ _ = rhs;
+ _ = allocator;
+ @panic("TODO implement Value.floatMod");
+ }
+
pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
// TODO is this a performance issue? maybe we should try the operation without
// resorting to BigInt first.
@@ -1875,6 +1979,31 @@ pub const Value = extern union {
return Tag.int_u64.create(arena, truncated);
}
+ pub fn shl(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
+ // TODO is this a performance issue? maybe we should try the operation without
+ // resorting to BigInt first.
+ var lhs_space: Value.BigIntSpace = undefined;
+ const lhs_bigint = lhs.toBigInt(&lhs_space);
+ const shift = rhs.toUnsignedInt();
+ const limbs = try allocator.alloc(
+ std.math.big.Limb,
+ lhs_bigint.limbs.len + (shift / (@sizeOf(std.math.big.Limb) * 8)) + 1,
+ );
+ var result_bigint = BigIntMutable{
+ .limbs = limbs,
+ .positive = undefined,
+ .len = undefined,
+ };
+ result_bigint.shiftLeft(lhs_bigint, shift);
+ const result_limbs = result_bigint.limbs[0..result_bigint.len];
+
+ if (result_bigint.positive) {
+ return Value.Tag.int_big_positive.create(allocator, result_limbs);
+ } else {
+ return Value.Tag.int_big_negative.create(allocator, result_limbs);
+ }
+ }
+
pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
// TODO is this a performance issue? maybe we should try the operation without
// resorting to BigInt first.
@@ -2227,4 +2356,13 @@ pub const Value = extern union {
/// are possible without using an allocator.
limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
};
+
+ pub const zero = initTag(.zero);
+ pub const one = initTag(.one);
+ pub const negative_one: Value = .{ .ptr_otherwise = &negative_one_payload.base };
+};
+
+var negative_one_payload: Value.Payload.I64 = .{
+ .base = .{ .tag = .int_i64 },
+ .data = -1,
};
diff --git a/test/behavior.zig b/test/behavior.zig
index 4bfd947fcf..479e1feffc 100644
--- a/test/behavior.zig
+++ b/test/behavior.zig
@@ -10,6 +10,7 @@ test {
_ = @import("behavior/eval.zig");
_ = @import("behavior/generics.zig");
_ = @import("behavior/if.zig");
+ _ = @import("behavior/math.zig");
_ = @import("behavior/member_func.zig");
_ = @import("behavior/pointers.zig");
_ = @import("behavior/sizeof_and_typeof.zig");
@@ -119,7 +120,7 @@ test {
_ = @import("behavior/incomplete_struct_param_tld.zig");
_ = @import("behavior/inttoptr.zig");
_ = @import("behavior/ir_block_deps.zig");
- _ = @import("behavior/math.zig");
+ _ = @import("behavior/math_stage1.zig");
_ = @import("behavior/maximum_minimum.zig");
_ = @import("behavior/merge_error_sets.zig");
_ = @import("behavior/misc.zig");
diff --git a/test/behavior/math.zig b/test/behavior/math.zig
index 7a5c31f67a..510cc3d438 100644
--- a/test/behavior/math.zig
+++ b/test/behavior/math.zig
@@ -6,171 +6,6 @@ const maxInt = std.math.maxInt;
const minInt = std.math.minInt;
const mem = std.mem;
-test "division" {
- try testDivision();
- comptime try testDivision();
-}
-fn testDivision() !void {
- try expect(div(u32, 13, 3) == 4);
- try expect(div(f16, 1.0, 2.0) == 0.5);
- try expect(div(f32, 1.0, 2.0) == 0.5);
-
- try expect(divExact(u32, 55, 11) == 5);
- try expect(divExact(i32, -55, 11) == -5);
- try expect(divExact(f16, 55.0, 11.0) == 5.0);
- try expect(divExact(f16, -55.0, 11.0) == -5.0);
- try expect(divExact(f32, 55.0, 11.0) == 5.0);
- try expect(divExact(f32, -55.0, 11.0) == -5.0);
-
- try expect(divFloor(i32, 5, 3) == 1);
- try expect(divFloor(i32, -5, 3) == -2);
- try expect(divFloor(f16, 5.0, 3.0) == 1.0);
- try expect(divFloor(f16, -5.0, 3.0) == -2.0);
- try expect(divFloor(f32, 5.0, 3.0) == 1.0);
- try expect(divFloor(f32, -5.0, 3.0) == -2.0);
- try expect(divFloor(i32, -0x80000000, -2) == 0x40000000);
- try expect(divFloor(i32, 0, -0x80000000) == 0);
- try expect(divFloor(i32, -0x40000001, 0x40000000) == -2);
- try expect(divFloor(i32, -0x80000000, 1) == -0x80000000);
- try expect(divFloor(i32, 10, 12) == 0);
- try expect(divFloor(i32, -14, 12) == -2);
- try expect(divFloor(i32, -2, 12) == -1);
-
- try expect(divTrunc(i32, 5, 3) == 1);
- try expect(divTrunc(i32, -5, 3) == -1);
- try expect(divTrunc(f16, 5.0, 3.0) == 1.0);
- try expect(divTrunc(f16, -5.0, 3.0) == -1.0);
- try expect(divTrunc(f32, 5.0, 3.0) == 1.0);
- try expect(divTrunc(f32, -5.0, 3.0) == -1.0);
- try expect(divTrunc(f64, 5.0, 3.0) == 1.0);
- try expect(divTrunc(f64, -5.0, 3.0) == -1.0);
- try expect(divTrunc(i32, 10, 12) == 0);
- try expect(divTrunc(i32, -14, 12) == -1);
- try expect(divTrunc(i32, -2, 12) == 0);
-
- try expect(mod(i32, 10, 12) == 10);
- try expect(mod(i32, -14, 12) == 10);
- try expect(mod(i32, -2, 12) == 10);
-
- comptime {
- try expect(
- 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600,
- );
- try expect(
- @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600,
- );
- try expect(
- 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2,
- );
- try expect(
- @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
- );
- try expect(
- @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2,
- );
- try expect(
- @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2,
- );
- try expect(
- 4126227191251978491697987544882340798050766755606969681711 % 10 == 1,
- );
- }
-}
-fn div(comptime T: type, a: T, b: T) T {
- return a / b;
-}
-fn divExact(comptime T: type, a: T, b: T) T {
- return @divExact(a, b);
-}
-fn divFloor(comptime T: type, a: T, b: T) T {
- return @divFloor(a, b);
-}
-fn divTrunc(comptime T: type, a: T, b: T) T {
- return @divTrunc(a, b);
-}
-fn mod(comptime T: type, a: T, b: T) T {
- return @mod(a, b);
-}
-
-test "@addWithOverflow" {
- var result: u8 = undefined;
- try expect(@addWithOverflow(u8, 250, 100, &result));
- try expect(!@addWithOverflow(u8, 100, 150, &result));
- try expect(result == 250);
-}
-
-// TODO test mulWithOverflow
-// TODO test subWithOverflow
-
-test "@shlWithOverflow" {
- var result: u16 = undefined;
- try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result));
- try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result));
- try expect(result == 0b1011111111111100);
-}
-
-test "@*WithOverflow with u0 values" {
- var result: u0 = undefined;
- try expect(!@addWithOverflow(u0, 0, 0, &result));
- try expect(!@subWithOverflow(u0, 0, 0, &result));
- try expect(!@mulWithOverflow(u0, 0, 0, &result));
- try expect(!@shlWithOverflow(u0, 0, 0, &result));
-}
-
-test "@clz" {
- try testClz();
- comptime try testClz();
-}
-
-fn testClz() !void {
- try expect(@clz(u8, 0b10001010) == 0);
- try expect(@clz(u8, 0b00001010) == 4);
- try expect(@clz(u8, 0b00011010) == 3);
- try expect(@clz(u8, 0b00000000) == 8);
- try expect(@clz(u128, 0xffffffffffffffff) == 64);
- try expect(@clz(u128, 0x10000000000000000) == 63);
-}
-
-test "@clz vectors" {
- try testClzVectors();
- comptime try testClzVectors();
-}
-
-fn testClzVectors() !void {
- @setEvalBranchQuota(10_000);
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0)));
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4)));
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3)));
- try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
- try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64)));
- try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63)));
-}
-
-test "@ctz" {
- try testCtz();
- comptime try testCtz();
-}
-
-fn testCtz() !void {
- try expect(@ctz(u8, 0b10100000) == 5);
- try expect(@ctz(u8, 0b10001010) == 1);
- try expect(@ctz(u8, 0b00000000) == 8);
- try expect(@ctz(u16, 0b00000000) == 16);
-}
-
-test "@ctz vectors" {
- try testClzVectors();
- comptime try testClzVectors();
-}
-
-fn testCtzVectors() !void {
- @setEvalBranchQuota(10_000);
- try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5)));
- try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1)));
- try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
- try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
-}
-
test "assignment operators" {
var i: u32 = 0;
i += 5;
@@ -218,686 +53,3 @@ fn testThreeExprInARow(f: bool, t: bool) !void {
fn assertFalse(b: bool) !void {
try expect(!b);
}
-
-test "const number literal" {
- const one = 1;
- const eleven = ten + one;
-
- try expect(eleven == 11);
-}
-const ten = 10;
-
-test "unsigned wrapping" {
- try testUnsignedWrappingEval(maxInt(u32));
- comptime try testUnsignedWrappingEval(maxInt(u32));
-}
-fn testUnsignedWrappingEval(x: u32) !void {
- const zero = x +% 1;
- try expect(zero == 0);
- const orig = zero -% 1;
- try expect(orig == maxInt(u32));
-}
-
-test "signed wrapping" {
- try testSignedWrappingEval(maxInt(i32));
- comptime try testSignedWrappingEval(maxInt(i32));
-}
-fn testSignedWrappingEval(x: i32) !void {
- const min_val = x +% 1;
- try expect(min_val == minInt(i32));
- const max_val = min_val -% 1;
- try expect(max_val == maxInt(i32));
-}
-
-test "signed negation wrapping" {
- try testSignedNegationWrappingEval(minInt(i16));
- comptime try testSignedNegationWrappingEval(minInt(i16));
-}
-fn testSignedNegationWrappingEval(x: i16) !void {
- try expect(x == -32768);
- const neg = -%x;
- try expect(neg == -32768);
-}
-
-test "unsigned negation wrapping" {
- try testUnsignedNegationWrappingEval(1);
- comptime try testUnsignedNegationWrappingEval(1);
-}
-fn testUnsignedNegationWrappingEval(x: u16) !void {
- try expect(x == 1);
- const neg = -%x;
- try expect(neg == maxInt(u16));
-}
-
-test "unsigned 64-bit division" {
- try test_u64_div();
- comptime try test_u64_div();
-}
-fn test_u64_div() !void {
- const result = divWithResult(1152921504606846976, 34359738365);
- try expect(result.quotient == 33554432);
- try expect(result.remainder == 100663296);
-}
-fn divWithResult(a: u64, b: u64) DivResult {
- return DivResult{
- .quotient = a / b,
- .remainder = a % b,
- };
-}
-const DivResult = struct {
- quotient: u64,
- remainder: u64,
-};
-
-test "binary not" {
- try expect(comptime x: {
- break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101;
- });
- try expect(comptime x: {
- break :x ~@as(u64, 2147483647) == 18446744071562067968;
- });
- try testBinaryNot(0b1010101010101010);
-}
-
-fn testBinaryNot(x: u16) !void {
- try expect(~x == 0b0101010101010101);
-}
-
-test "small int addition" {
- var x: u2 = 0;
- try expect(x == 0);
-
- x += 1;
- try expect(x == 1);
-
- x += 1;
- try expect(x == 2);
-
- x += 1;
- try expect(x == 3);
-
- var result: @TypeOf(x) = 3;
- try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
-
- try expect(result == 0);
-}
-
-test "float equality" {
- const x: f64 = 0.012;
- const y: f64 = x + 1.0;
-
- try testFloatEqualityImpl(x, y);
- comptime try testFloatEqualityImpl(x, y);
-}
-
-fn testFloatEqualityImpl(x: f64, y: f64) !void {
- const y2 = x + 1.0;
- try expect(y == y2);
-}
-
-test "allow signed integer division/remainder when values are comptime known and positive or exact" {
- try expect(5 / 3 == 1);
- try expect(-5 / -3 == 1);
- try expect(-6 / 3 == -2);
-
- try expect(5 % 3 == 2);
- try expect(-6 % 3 == 0);
-}
-
-test "hex float literal parsing" {
- comptime try expect(0x1.0 == 1.0);
-}
-
-test "quad hex float literal parsing in range" {
- const a = 0x1.af23456789bbaaab347645365cdep+5;
- const b = 0x1.dedafcff354b6ae9758763545432p-9;
- const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534;
- const d = 0x1.edcbff8ad76ab5bf46463233214fp-435;
- if (false) {
- a;
- b;
- c;
- d;
- }
-}
-
-test "quad hex float literal parsing accurate" {
- const a: f128 = 0x1.1111222233334444555566667777p+0;
-
- // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing.
- const expected: u128 = 0x3fff1111222233334444555566667777;
- try expect(@bitCast(u128, a) == expected);
-
- // non-normalized
- const b: f128 = 0x11.111222233334444555566667777p-4;
- try expect(@bitCast(u128, b) == expected);
-
- const S = struct {
- fn doTheTest() !void {
- {
- var f: f128 = 0x1.2eab345678439abcdefea56782346p+5;
- try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234);
- }
- {
- var f: f128 = 0x1.edcb34a235253948765432134674fp-1;
- try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674);
- }
- {
- var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50;
- try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50);
- }
- {
- var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9;
- try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568);
- }
- const exp2ft = [_]f64{
- 0x1.6a09e667f3bcdp-1,
- 0x1.7a11473eb0187p-1,
- 0x1.8ace5422aa0dbp-1,
- 0x1.9c49182a3f090p-1,
- 0x1.ae89f995ad3adp-1,
- 0x1.c199bdd85529cp-1,
- 0x1.d5818dcfba487p-1,
- 0x1.ea4afa2a490dap-1,
- 0x1.0000000000000p+0,
- 0x1.0b5586cf9890fp+0,
- 0x1.172b83c7d517bp+0,
- 0x1.2387a6e756238p+0,
- 0x1.306fe0a31b715p+0,
- 0x1.3dea64c123422p+0,
- 0x1.4bfdad5362a27p+0,
- 0x1.5ab07dd485429p+0,
- 0x1.8p23,
- 0x1.62e430p-1,
- 0x1.ebfbe0p-3,
- 0x1.c6b348p-5,
- 0x1.3b2c9cp-7,
- 0x1.0p127,
- -0x1.0p-149,
- };
-
- const answers = [_]u64{
- 0x3fe6a09e667f3bcd,
- 0x3fe7a11473eb0187,
- 0x3fe8ace5422aa0db,
- 0x3fe9c49182a3f090,
- 0x3feae89f995ad3ad,
- 0x3fec199bdd85529c,
- 0x3fed5818dcfba487,
- 0x3feea4afa2a490da,
- 0x3ff0000000000000,
- 0x3ff0b5586cf9890f,
- 0x3ff172b83c7d517b,
- 0x3ff2387a6e756238,
- 0x3ff306fe0a31b715,
- 0x3ff3dea64c123422,
- 0x3ff4bfdad5362a27,
- 0x3ff5ab07dd485429,
- 0x4168000000000000,
- 0x3fe62e4300000000,
- 0x3fcebfbe00000000,
- 0x3fac6b3480000000,
- 0x3f83b2c9c0000000,
- 0x47e0000000000000,
- 0xb6a0000000000000,
- };
-
- for (exp2ft) |x, i| {
- try expect(@bitCast(u64, x) == answers[i]);
- }
- }
- };
- try S.doTheTest();
- comptime try S.doTheTest();
-}
-
-test "underscore separator parsing" {
- try expect(0_0_0_0 == 0);
- try expect(1_234_567 == 1234567);
- try expect(001_234_567 == 1234567);
- try expect(0_0_1_2_3_4_5_6_7 == 1234567);
-
- try expect(0b0_0_0_0 == 0);
- try expect(0b1010_1010 == 0b10101010);
- try expect(0b0000_1010_1010 == 0b10101010);
- try expect(0b1_0_1_0_1_0_1_0 == 0b10101010);
-
- try expect(0o0_0_0_0 == 0);
- try expect(0o1010_1010 == 0o10101010);
- try expect(0o0000_1010_1010 == 0o10101010);
- try expect(0o1_0_1_0_1_0_1_0 == 0o10101010);
-
- try expect(0x0_0_0_0 == 0);
- try expect(0x1010_1010 == 0x10101010);
- try expect(0x0000_1010_1010 == 0x10101010);
- try expect(0x1_0_1_0_1_0_1_0 == 0x10101010);
-
- try expect(123_456.789_000e1_0 == 123456.789000e10);
- try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10);
-
- try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10);
- try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10);
-}
-
-test "hex float literal within range" {
- const a = 0x1.0p16383;
- const b = 0x0.1p16387;
- const c = 0x1.0p-16382;
- if (false) {
- a;
- b;
- c;
- }
-}
-
-test "truncating shift left" {
- try testShlTrunc(maxInt(u16));
- comptime try testShlTrunc(maxInt(u16));
-}
-fn testShlTrunc(x: u16) !void {
- const shifted = x << 1;
- try expect(shifted == 65534);
-}
-
-test "truncating shift right" {
- try testShrTrunc(maxInt(u16));
- comptime try testShrTrunc(maxInt(u16));
-}
-fn testShrTrunc(x: u16) !void {
- const shifted = x >> 1;
- try expect(shifted == 32767);
-}
-
-test "exact shift left" {
- try testShlExact(0b00110101);
- comptime try testShlExact(0b00110101);
-}
-fn testShlExact(x: u8) !void {
- const shifted = @shlExact(x, 2);
- try expect(shifted == 0b11010100);
-}
-
-test "exact shift right" {
- try testShrExact(0b10110100);
- comptime try testShrExact(0b10110100);
-}
-fn testShrExact(x: u8) !void {
- const shifted = @shrExact(x, 2);
- try expect(shifted == 0b00101101);
-}
-
-test "shift left/right on u0 operand" {
- const S = struct {
- fn doTheTest() !void {
- var x: u0 = 0;
- var y: u0 = 0;
- try expectEqual(@as(u0, 0), x << 0);
- try expectEqual(@as(u0, 0), x >> 0);
- try expectEqual(@as(u0, 0), x << y);
- try expectEqual(@as(u0, 0), x >> y);
- try expectEqual(@as(u0, 0), @shlExact(x, 0));
- try expectEqual(@as(u0, 0), @shrExact(x, 0));
- try expectEqual(@as(u0, 0), @shlExact(x, y));
- try expectEqual(@as(u0, 0), @shrExact(x, y));
- }
- };
- try S.doTheTest();
- comptime try S.doTheTest();
-}
-
-test "comptime_int addition" {
- comptime {
- try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950);
- try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380);
- }
-}
-
-test "comptime_int multiplication" {
- comptime {
- try expect(
- 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567,
- );
- try expect(
- 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016,
- );
- }
-}
-
-test "comptime_int shifting" {
- comptime {
- try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000);
- }
-}
-
-test "comptime_int multi-limb shift and mask" {
- comptime {
- var a = 0xefffffffa0000001eeeeeeefaaaaaaab;
-
- try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab);
- a >>= 32;
- try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef);
- a >>= 32;
- try expect(@as(u32, a & 0xffffffff) == 0xa0000001);
- a >>= 32;
- try expect(@as(u32, a & 0xffffffff) == 0xefffffff);
- a >>= 32;
-
- try expect(a == 0);
- }
-}
-
-test "comptime_int multi-limb partial shift right" {
- comptime {
- var a = 0x1ffffffffeeeeeeee;
- a >>= 16;
- try expect(a == 0x1ffffffffeeee);
- }
-}
-
-test "xor" {
- try test_xor();
- comptime try test_xor();
-}
-
-fn test_xor() !void {
- try expect(0xFF ^ 0x00 == 0xFF);
- try expect(0xF0 ^ 0x0F == 0xFF);
- try expect(0xFF ^ 0xF0 == 0x0F);
- try expect(0xFF ^ 0x0F == 0xF0);
- try expect(0xFF ^ 0xFF == 0x00);
-}
-
-test "comptime_int xor" {
- comptime {
- try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
- try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
- try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF);
- try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000);
- try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000);
- try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
- try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF);
- try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000);
- }
-}
-
-test "f128" {
- try test_f128();
- comptime try test_f128();
-}
-
-fn make_f128(x: f128) f128 {
- return x;
-}
-
-fn test_f128() !void {
- try expect(@sizeOf(f128) == 16);
- try expect(make_f128(1.0) == 1.0);
- try expect(make_f128(1.0) != 1.1);
- try expect(make_f128(1.0) > 0.9);
- try expect(make_f128(1.0) >= 0.9);
- try expect(make_f128(1.0) >= 1.0);
- try should_not_be_zero(1.0);
-}
-
-fn should_not_be_zero(x: f128) !void {
- try expect(x != 0.0);
-}
-
-test "comptime float rem int" {
- comptime {
- var x = @as(f32, 1) % 2;
- try expect(x == 1.0);
- }
-}
-
-test "remainder division" {
- comptime try remdiv(f16);
- comptime try remdiv(f32);
- comptime try remdiv(f64);
- comptime try remdiv(f128);
- try remdiv(f16);
- try remdiv(f64);
- try remdiv(f128);
-}
-
-fn remdiv(comptime T: type) !void {
- try expect(@as(T, 1) == @as(T, 1) % @as(T, 2));
- try expect(@as(T, 1) == @as(T, 7) % @as(T, 3));
-}
-
-test "@sqrt" {
- try testSqrt(f64, 12.0);
- comptime try testSqrt(f64, 12.0);
- try testSqrt(f32, 13.0);
- comptime try testSqrt(f32, 13.0);
- try testSqrt(f16, 13.0);
- comptime try testSqrt(f16, 13.0);
-
- const x = 14.0;
- const y = x * x;
- const z = @sqrt(y);
- comptime try expect(z == x);
-}
-
-fn testSqrt(comptime T: type, x: T) !void {
- try expect(@sqrt(x * x) == x);
-}
-
-test "@fabs" {
- try testFabs(f128, 12.0);
- comptime try testFabs(f128, 12.0);
- try testFabs(f64, 12.0);
- comptime try testFabs(f64, 12.0);
- try testFabs(f32, 12.0);
- comptime try testFabs(f32, 12.0);
- try testFabs(f16, 12.0);
- comptime try testFabs(f16, 12.0);
-
- const x = 14.0;
- const y = -x;
- const z = @fabs(y);
- comptime try expectEqual(x, z);
-}
-
-fn testFabs(comptime T: type, x: T) !void {
- const y = -x;
- const z = @fabs(y);
- try expectEqual(x, z);
-}
-
-test "@floor" {
- // FIXME: Generates a floorl function call
- // testFloor(f128, 12.0);
- comptime try testFloor(f128, 12.0);
- try testFloor(f64, 12.0);
- comptime try testFloor(f64, 12.0);
- try testFloor(f32, 12.0);
- comptime try testFloor(f32, 12.0);
- try testFloor(f16, 12.0);
- comptime try testFloor(f16, 12.0);
-
- const x = 14.0;
- const y = x + 0.7;
- const z = @floor(y);
- comptime try expectEqual(x, z);
-}
-
-fn testFloor(comptime T: type, x: T) !void {
- const y = x + 0.6;
- const z = @floor(y);
- try expectEqual(x, z);
-}
-
-test "@ceil" {
- // FIXME: Generates a ceill function call
- //testCeil(f128, 12.0);
- comptime try testCeil(f128, 12.0);
- try testCeil(f64, 12.0);
- comptime try testCeil(f64, 12.0);
- try testCeil(f32, 12.0);
- comptime try testCeil(f32, 12.0);
- try testCeil(f16, 12.0);
- comptime try testCeil(f16, 12.0);
-
- const x = 14.0;
- const y = x - 0.7;
- const z = @ceil(y);
- comptime try expectEqual(x, z);
-}
-
-fn testCeil(comptime T: type, x: T) !void {
- const y = x - 0.8;
- const z = @ceil(y);
- try expectEqual(x, z);
-}
-
-test "@trunc" {
- // FIXME: Generates a truncl function call
- //testTrunc(f128, 12.0);
- comptime try testTrunc(f128, 12.0);
- try testTrunc(f64, 12.0);
- comptime try testTrunc(f64, 12.0);
- try testTrunc(f32, 12.0);
- comptime try testTrunc(f32, 12.0);
- try testTrunc(f16, 12.0);
- comptime try testTrunc(f16, 12.0);
-
- const x = 14.0;
- const y = x + 0.7;
- const z = @trunc(y);
- comptime try expectEqual(x, z);
-}
-
-fn testTrunc(comptime T: type, x: T) !void {
- {
- const y = x + 0.8;
- const z = @trunc(y);
- try expectEqual(x, z);
- }
-
- {
- const y = -x - 0.8;
- const z = @trunc(y);
- try expectEqual(-x, z);
- }
-}
-
-test "@round" {
- // FIXME: Generates a roundl function call
- //testRound(f128, 12.0);
- comptime try testRound(f128, 12.0);
- try testRound(f64, 12.0);
- comptime try testRound(f64, 12.0);
- try testRound(f32, 12.0);
- comptime try testRound(f32, 12.0);
- try testRound(f16, 12.0);
- comptime try testRound(f16, 12.0);
-
- const x = 14.0;
- const y = x + 0.4;
- const z = @round(y);
- comptime try expectEqual(x, z);
-}
-
-fn testRound(comptime T: type, x: T) !void {
- const y = x - 0.5;
- const z = @round(y);
- try expectEqual(x, z);
-}
-
-test "comptime_int param and return" {
- const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702);
- try expect(a == 137114567242441932203689521744947848950);
-
- const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768);
- try expect(b == 985095453608931032642182098849559179469148836107390954364380);
-}
-
-fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int {
- return a + b;
-}
-
-test "vector integer addition" {
- const S = struct {
- fn doTheTest() !void {
- var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 };
- var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 };
- var result = a + b;
- var result_array: [4]i32 = result;
- const expected = [_]i32{ 6, 8, 10, 12 };
- try expectEqualSlices(i32, &expected, &result_array);
- }
- };
- try S.doTheTest();
- comptime try S.doTheTest();
-}
-
-test "NaN comparison" {
- try testNanEqNan(f16);
- try testNanEqNan(f32);
- try testNanEqNan(f64);
- try testNanEqNan(f128);
- comptime try testNanEqNan(f16);
- comptime try testNanEqNan(f32);
- comptime try testNanEqNan(f64);
- comptime try testNanEqNan(f128);
-}
-
-fn testNanEqNan(comptime F: type) !void {
- var nan1 = std.math.nan(F);
- var nan2 = std.math.nan(F);
- try expect(nan1 != nan2);
- try expect(!(nan1 == nan2));
- try expect(!(nan1 > nan2));
- try expect(!(nan1 >= nan2));
- try expect(!(nan1 < nan2));
- try expect(!(nan1 <= nan2));
-}
-
-test "128-bit multiplication" {
- var a: i128 = 3;
- var b: i128 = 2;
- var c = a * b;
- try expect(c == 6);
-}
-
-test "vector comparison" {
- const S = struct {
- fn doTheTest() !void {
- var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 };
- var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 };
- try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false }));
- try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false }));
- try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false }));
- try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true }));
- try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true }));
- try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true }));
- }
- };
- try S.doTheTest();
- comptime try S.doTheTest();
-}
-
-test "compare undefined literal with comptime_int" {
- var x = undefined == 1;
- // x is now undefined with type bool
- x = true;
- try expect(x);
-}
-
-test "signed zeros are represented properly" {
- const S = struct {
- fn doTheTest() !void {
- inline for ([_]type{ f16, f32, f64, f128 }) |T| {
- const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);
- var as_fp_val = -@as(T, 0.0);
- var as_uint_val = @bitCast(ST, as_fp_val);
- // Ensure the sign bit is set.
- try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1);
- }
- }
- };
-
- try S.doTheTest();
- comptime try S.doTheTest();
-}
diff --git a/test/behavior/math_stage1.zig b/test/behavior/math_stage1.zig
new file mode 100644
index 0000000000..9f412930b5
--- /dev/null
+++ b/test/behavior/math_stage1.zig
@@ -0,0 +1,855 @@
+const std = @import("std");
+const expect = std.testing.expect;
+const expectEqual = std.testing.expectEqual;
+const expectEqualSlices = std.testing.expectEqualSlices;
+const maxInt = std.math.maxInt;
+const minInt = std.math.minInt;
+const mem = std.mem;
+
+test "division" {
+ try testDivision();
+ comptime try testDivision();
+}
+fn testDivision() !void {
+ try expect(div(u32, 13, 3) == 4);
+ try expect(div(f16, 1.0, 2.0) == 0.5);
+ try expect(div(f32, 1.0, 2.0) == 0.5);
+
+ try expect(divExact(u32, 55, 11) == 5);
+ try expect(divExact(i32, -55, 11) == -5);
+ try expect(divExact(f16, 55.0, 11.0) == 5.0);
+ try expect(divExact(f16, -55.0, 11.0) == -5.0);
+ try expect(divExact(f32, 55.0, 11.0) == 5.0);
+ try expect(divExact(f32, -55.0, 11.0) == -5.0);
+
+ try expect(divFloor(i32, 5, 3) == 1);
+ try expect(divFloor(i32, -5, 3) == -2);
+ try expect(divFloor(f16, 5.0, 3.0) == 1.0);
+ try expect(divFloor(f16, -5.0, 3.0) == -2.0);
+ try expect(divFloor(f32, 5.0, 3.0) == 1.0);
+ try expect(divFloor(f32, -5.0, 3.0) == -2.0);
+ try expect(divFloor(i32, -0x80000000, -2) == 0x40000000);
+ try expect(divFloor(i32, 0, -0x80000000) == 0);
+ try expect(divFloor(i32, -0x40000001, 0x40000000) == -2);
+ try expect(divFloor(i32, -0x80000000, 1) == -0x80000000);
+ try expect(divFloor(i32, 10, 12) == 0);
+ try expect(divFloor(i32, -14, 12) == -2);
+ try expect(divFloor(i32, -2, 12) == -1);
+
+ try expect(divTrunc(i32, 5, 3) == 1);
+ try expect(divTrunc(i32, -5, 3) == -1);
+ try expect(divTrunc(f16, 5.0, 3.0) == 1.0);
+ try expect(divTrunc(f16, -5.0, 3.0) == -1.0);
+ try expect(divTrunc(f32, 5.0, 3.0) == 1.0);
+ try expect(divTrunc(f32, -5.0, 3.0) == -1.0);
+ try expect(divTrunc(f64, 5.0, 3.0) == 1.0);
+ try expect(divTrunc(f64, -5.0, 3.0) == -1.0);
+ try expect(divTrunc(i32, 10, 12) == 0);
+ try expect(divTrunc(i32, -14, 12) == -1);
+ try expect(divTrunc(i32, -2, 12) == 0);
+
+ try expect(mod(i32, 10, 12) == 10);
+ try expect(mod(i32, -14, 12) == 10);
+ try expect(mod(i32, -2, 12) == 10);
+
+ comptime {
+ try expect(
+ 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600,
+ );
+ try expect(
+ @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600,
+ );
+ try expect(
+ 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2,
+ );
+ try expect(
+ @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
+ );
+ try expect(
+ @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2,
+ );
+ try expect(
+ @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2,
+ );
+ try expect(
+ 4126227191251978491697987544882340798050766755606969681711 % 10 == 1,
+ );
+ }
+}
+fn div(comptime T: type, a: T, b: T) T {
+ return a / b;
+}
+fn divExact(comptime T: type, a: T, b: T) T {
+ return @divExact(a, b);
+}
+fn divFloor(comptime T: type, a: T, b: T) T {
+ return @divFloor(a, b);
+}
+fn divTrunc(comptime T: type, a: T, b: T) T {
+ return @divTrunc(a, b);
+}
+fn mod(comptime T: type, a: T, b: T) T {
+ return @mod(a, b);
+}
+
+test "@addWithOverflow" {
+ var result: u8 = undefined;
+ try expect(@addWithOverflow(u8, 250, 100, &result));
+ try expect(!@addWithOverflow(u8, 100, 150, &result));
+ try expect(result == 250);
+}
+
+// TODO test mulWithOverflow
+// TODO test subWithOverflow
+
+test "@shlWithOverflow" {
+ var result: u16 = undefined;
+ try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result));
+ try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result));
+ try expect(result == 0b1011111111111100);
+}
+
+test "@*WithOverflow with u0 values" {
+ var result: u0 = undefined;
+ try expect(!@addWithOverflow(u0, 0, 0, &result));
+ try expect(!@subWithOverflow(u0, 0, 0, &result));
+ try expect(!@mulWithOverflow(u0, 0, 0, &result));
+ try expect(!@shlWithOverflow(u0, 0, 0, &result));
+}
+
+test "@clz" {
+ try testClz();
+ comptime try testClz();
+}
+
+fn testClz() !void {
+ try expect(@clz(u8, 0b10001010) == 0);
+ try expect(@clz(u8, 0b00001010) == 4);
+ try expect(@clz(u8, 0b00011010) == 3);
+ try expect(@clz(u8, 0b00000000) == 8);
+ try expect(@clz(u128, 0xffffffffffffffff) == 64);
+ try expect(@clz(u128, 0x10000000000000000) == 63);
+}
+
+test "@clz vectors" {
+ try testClzVectors();
+ comptime try testClzVectors();
+}
+
+fn testClzVectors() !void {
+ @setEvalBranchQuota(10_000);
+ try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0)));
+ try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4)));
+ try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3)));
+ try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
+ try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64)));
+ try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63)));
+}
+
+test "@ctz" {
+ try testCtz();
+ comptime try testCtz();
+}
+
+fn testCtz() !void {
+ try expect(@ctz(u8, 0b10100000) == 5);
+ try expect(@ctz(u8, 0b10001010) == 1);
+ try expect(@ctz(u8, 0b00000000) == 8);
+ try expect(@ctz(u16, 0b00000000) == 16);
+}
+
+test "@ctz vectors" {
+ try testClzVectors();
+ comptime try testClzVectors();
+}
+
+fn testCtzVectors() !void {
+ @setEvalBranchQuota(10_000);
+ try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5)));
+ try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1)));
+ try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8)));
+ try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
+}
+
+test "const number literal" {
+ const one = 1;
+ const eleven = ten + one;
+
+ try expect(eleven == 11);
+}
+const ten = 10;
+
+test "unsigned wrapping" {
+ try testUnsignedWrappingEval(maxInt(u32));
+ comptime try testUnsignedWrappingEval(maxInt(u32));
+}
+fn testUnsignedWrappingEval(x: u32) !void {
+ const zero = x +% 1;
+ try expect(zero == 0);
+ const orig = zero -% 1;
+ try expect(orig == maxInt(u32));
+}
+
+test "signed wrapping" {
+ try testSignedWrappingEval(maxInt(i32));
+ comptime try testSignedWrappingEval(maxInt(i32));
+}
+fn testSignedWrappingEval(x: i32) !void {
+ const min_val = x +% 1;
+ try expect(min_val == minInt(i32));
+ const max_val = min_val -% 1;
+ try expect(max_val == maxInt(i32));
+}
+
+test "signed negation wrapping" {
+ try testSignedNegationWrappingEval(minInt(i16));
+ comptime try testSignedNegationWrappingEval(minInt(i16));
+}
+fn testSignedNegationWrappingEval(x: i16) !void {
+ try expect(x == -32768);
+ const neg = -%x;
+ try expect(neg == -32768);
+}
+
+test "unsigned negation wrapping" {
+ try testUnsignedNegationWrappingEval(1);
+ comptime try testUnsignedNegationWrappingEval(1);
+}
+fn testUnsignedNegationWrappingEval(x: u16) !void {
+ try expect(x == 1);
+ const neg = -%x;
+ try expect(neg == maxInt(u16));
+}
+
+test "unsigned 64-bit division" {
+ try test_u64_div();
+ comptime try test_u64_div();
+}
+fn test_u64_div() !void {
+ const result = divWithResult(1152921504606846976, 34359738365);
+ try expect(result.quotient == 33554432);
+ try expect(result.remainder == 100663296);
+}
+fn divWithResult(a: u64, b: u64) DivResult {
+ return DivResult{
+ .quotient = a / b,
+ .remainder = a % b,
+ };
+}
+const DivResult = struct {
+ quotient: u64,
+ remainder: u64,
+};
+
+test "binary not" {
+ try expect(comptime x: {
+ break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101;
+ });
+ try expect(comptime x: {
+ break :x ~@as(u64, 2147483647) == 18446744071562067968;
+ });
+ try testBinaryNot(0b1010101010101010);
+}
+
+fn testBinaryNot(x: u16) !void {
+ try expect(~x == 0b0101010101010101);
+}
+
+test "small int addition" {
+ var x: u2 = 0;
+ try expect(x == 0);
+
+ x += 1;
+ try expect(x == 1);
+
+ x += 1;
+ try expect(x == 2);
+
+ x += 1;
+ try expect(x == 3);
+
+ var result: @TypeOf(x) = 3;
+ try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
+
+ try expect(result == 0);
+}
+
+test "float equality" {
+ const x: f64 = 0.012;
+ const y: f64 = x + 1.0;
+
+ try testFloatEqualityImpl(x, y);
+ comptime try testFloatEqualityImpl(x, y);
+}
+
+fn testFloatEqualityImpl(x: f64, y: f64) !void {
+ const y2 = x + 1.0;
+ try expect(y == y2);
+}
+
+test "allow signed integer division/remainder when values are comptime known and positive or exact" {
+ try expect(5 / 3 == 1);
+ try expect(-5 / -3 == 1);
+ try expect(-6 / 3 == -2);
+
+ try expect(5 % 3 == 2);
+ try expect(-6 % 3 == 0);
+}
+
+test "hex float literal parsing" {
+ comptime try expect(0x1.0 == 1.0);
+}
+
+test "quad hex float literal parsing in range" {
+ const a = 0x1.af23456789bbaaab347645365cdep+5;
+ const b = 0x1.dedafcff354b6ae9758763545432p-9;
+ const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534;
+ const d = 0x1.edcbff8ad76ab5bf46463233214fp-435;
+ if (false) {
+ a;
+ b;
+ c;
+ d;
+ }
+}
+
+test "quad hex float literal parsing accurate" {
+ const a: f128 = 0x1.1111222233334444555566667777p+0;
+
+ // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing.
+ const expected: u128 = 0x3fff1111222233334444555566667777;
+ try expect(@bitCast(u128, a) == expected);
+
+ // non-normalized
+ const b: f128 = 0x11.111222233334444555566667777p-4;
+ try expect(@bitCast(u128, b) == expected);
+
+ const S = struct {
+ fn doTheTest() !void {
+ {
+ var f: f128 = 0x1.2eab345678439abcdefea56782346p+5;
+ try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234);
+ }
+ {
+ var f: f128 = 0x1.edcb34a235253948765432134674fp-1;
+ try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674);
+ }
+ {
+ var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50;
+ try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50);
+ }
+ {
+ var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9;
+ try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568);
+ }
+ const exp2ft = [_]f64{
+ 0x1.6a09e667f3bcdp-1,
+ 0x1.7a11473eb0187p-1,
+ 0x1.8ace5422aa0dbp-1,
+ 0x1.9c49182a3f090p-1,
+ 0x1.ae89f995ad3adp-1,
+ 0x1.c199bdd85529cp-1,
+ 0x1.d5818dcfba487p-1,
+ 0x1.ea4afa2a490dap-1,
+ 0x1.0000000000000p+0,
+ 0x1.0b5586cf9890fp+0,
+ 0x1.172b83c7d517bp+0,
+ 0x1.2387a6e756238p+0,
+ 0x1.306fe0a31b715p+0,
+ 0x1.3dea64c123422p+0,
+ 0x1.4bfdad5362a27p+0,
+ 0x1.5ab07dd485429p+0,
+ 0x1.8p23,
+ 0x1.62e430p-1,
+ 0x1.ebfbe0p-3,
+ 0x1.c6b348p-5,
+ 0x1.3b2c9cp-7,
+ 0x1.0p127,
+ -0x1.0p-149,
+ };
+
+ const answers = [_]u64{
+ 0x3fe6a09e667f3bcd,
+ 0x3fe7a11473eb0187,
+ 0x3fe8ace5422aa0db,
+ 0x3fe9c49182a3f090,
+ 0x3feae89f995ad3ad,
+ 0x3fec199bdd85529c,
+ 0x3fed5818dcfba487,
+ 0x3feea4afa2a490da,
+ 0x3ff0000000000000,
+ 0x3ff0b5586cf9890f,
+ 0x3ff172b83c7d517b,
+ 0x3ff2387a6e756238,
+ 0x3ff306fe0a31b715,
+ 0x3ff3dea64c123422,
+ 0x3ff4bfdad5362a27,
+ 0x3ff5ab07dd485429,
+ 0x4168000000000000,
+ 0x3fe62e4300000000,
+ 0x3fcebfbe00000000,
+ 0x3fac6b3480000000,
+ 0x3f83b2c9c0000000,
+ 0x47e0000000000000,
+ 0xb6a0000000000000,
+ };
+
+ for (exp2ft) |x, i| {
+ try expect(@bitCast(u64, x) == answers[i]);
+ }
+ }
+ };
+ try S.doTheTest();
+ comptime try S.doTheTest();
+}
+
+test "underscore separator parsing" {
+ try expect(0_0_0_0 == 0);
+ try expect(1_234_567 == 1234567);
+ try expect(001_234_567 == 1234567);
+ try expect(0_0_1_2_3_4_5_6_7 == 1234567);
+
+ try expect(0b0_0_0_0 == 0);
+ try expect(0b1010_1010 == 0b10101010);
+ try expect(0b0000_1010_1010 == 0b10101010);
+ try expect(0b1_0_1_0_1_0_1_0 == 0b10101010);
+
+ try expect(0o0_0_0_0 == 0);
+ try expect(0o1010_1010 == 0o10101010);
+ try expect(0o0000_1010_1010 == 0o10101010);
+ try expect(0o1_0_1_0_1_0_1_0 == 0o10101010);
+
+ try expect(0x0_0_0_0 == 0);
+ try expect(0x1010_1010 == 0x10101010);
+ try expect(0x0000_1010_1010 == 0x10101010);
+ try expect(0x1_0_1_0_1_0_1_0 == 0x10101010);
+
+ try expect(123_456.789_000e1_0 == 123456.789000e10);
+ try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10);
+
+ try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10);
+ try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10);
+}
+
+test "hex float literal within range" {
+ const a = 0x1.0p16383;
+ const b = 0x0.1p16387;
+ const c = 0x1.0p-16382;
+ if (false) {
+ a;
+ b;
+ c;
+ }
+}
+
+test "truncating shift left" {
+ try testShlTrunc(maxInt(u16));
+ comptime try testShlTrunc(maxInt(u16));
+}
+fn testShlTrunc(x: u16) !void {
+ const shifted = x << 1;
+ try expect(shifted == 65534);
+}
+
+test "truncating shift right" {
+ try testShrTrunc(maxInt(u16));
+ comptime try testShrTrunc(maxInt(u16));
+}
+fn testShrTrunc(x: u16) !void {
+ const shifted = x >> 1;
+ try expect(shifted == 32767);
+}
+
+test "exact shift left" {
+ try testShlExact(0b00110101);
+ comptime try testShlExact(0b00110101);
+}
+fn testShlExact(x: u8) !void {
+ const shifted = @shlExact(x, 2);
+ try expect(shifted == 0b11010100);
+}
+
+test "exact shift right" {
+ try testShrExact(0b10110100);
+ comptime try testShrExact(0b10110100);
+}
+fn testShrExact(x: u8) !void {
+ const shifted = @shrExact(x, 2);
+ try expect(shifted == 0b00101101);
+}
+
+test "shift left/right on u0 operand" {
+ const S = struct {
+ fn doTheTest() !void {
+ var x: u0 = 0;
+ var y: u0 = 0;
+ try expectEqual(@as(u0, 0), x << 0);
+ try expectEqual(@as(u0, 0), x >> 0);
+ try expectEqual(@as(u0, 0), x << y);
+ try expectEqual(@as(u0, 0), x >> y);
+ try expectEqual(@as(u0, 0), @shlExact(x, 0));
+ try expectEqual(@as(u0, 0), @shrExact(x, 0));
+ try expectEqual(@as(u0, 0), @shlExact(x, y));
+ try expectEqual(@as(u0, 0), @shrExact(x, y));
+ }
+ };
+ try S.doTheTest();
+ comptime try S.doTheTest();
+}
+
+test "comptime_int addition" {
+ comptime {
+ try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950);
+ try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380);
+ }
+}
+
+test "comptime_int multiplication" {
+ comptime {
+ try expect(
+ 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567,
+ );
+ try expect(
+ 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016,
+ );
+ }
+}
+
+test "comptime_int shifting" {
+ comptime {
+ try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000);
+ }
+}
+
+test "comptime_int multi-limb shift and mask" {
+ comptime {
+ var a = 0xefffffffa0000001eeeeeeefaaaaaaab;
+
+ try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab);
+ a >>= 32;
+ try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef);
+ a >>= 32;
+ try expect(@as(u32, a & 0xffffffff) == 0xa0000001);
+ a >>= 32;
+ try expect(@as(u32, a & 0xffffffff) == 0xefffffff);
+ a >>= 32;
+
+ try expect(a == 0);
+ }
+}
+
+test "comptime_int multi-limb partial shift right" {
+ comptime {
+ var a = 0x1ffffffffeeeeeeee;
+ a >>= 16;
+ try expect(a == 0x1ffffffffeeee);
+ }
+}
+
+test "xor" {
+ try test_xor();
+ comptime try test_xor();
+}
+
+fn test_xor() !void {
+ try expect(0xFF ^ 0x00 == 0xFF);
+ try expect(0xF0 ^ 0x0F == 0xFF);
+ try expect(0xFF ^ 0xF0 == 0x0F);
+ try expect(0xFF ^ 0x0F == 0xF0);
+ try expect(0xFF ^ 0xFF == 0x00);
+}
+
+test "comptime_int xor" {
+ comptime {
+ try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+ try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+ try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF);
+ try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000);
+ try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000);
+ try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+ try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF);
+ try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000);
+ }
+}
+
+test "f128" {
+ try test_f128();
+ comptime try test_f128();
+}
+
+fn make_f128(x: f128) f128 {
+ return x;
+}
+
+fn test_f128() !void {
+ try expect(@sizeOf(f128) == 16);
+ try expect(make_f128(1.0) == 1.0);
+ try expect(make_f128(1.0) != 1.1);
+ try expect(make_f128(1.0) > 0.9);
+ try expect(make_f128(1.0) >= 0.9);
+ try expect(make_f128(1.0) >= 1.0);
+ try should_not_be_zero(1.0);
+}
+
+fn should_not_be_zero(x: f128) !void {
+ try expect(x != 0.0);
+}
+
+test "comptime float rem int" {
+ comptime {
+ var x = @as(f32, 1) % 2;
+ try expect(x == 1.0);
+ }
+}
+
+test "remainder division" {
+ comptime try remdiv(f16);
+ comptime try remdiv(f32);
+ comptime try remdiv(f64);
+ comptime try remdiv(f128);
+ try remdiv(f16);
+ try remdiv(f64);
+ try remdiv(f128);
+}
+
+fn remdiv(comptime T: type) !void {
+ try expect(@as(T, 1) == @as(T, 1) % @as(T, 2));
+ try expect(@as(T, 1) == @as(T, 7) % @as(T, 3));
+}
+
+test "@sqrt" {
+ try testSqrt(f64, 12.0);
+ comptime try testSqrt(f64, 12.0);
+ try testSqrt(f32, 13.0);
+ comptime try testSqrt(f32, 13.0);
+ try testSqrt(f16, 13.0);
+ comptime try testSqrt(f16, 13.0);
+
+ const x = 14.0;
+ const y = x * x;
+ const z = @sqrt(y);
+ comptime try expect(z == x);
+}
+
+fn testSqrt(comptime T: type, x: T) !void {
+ try expect(@sqrt(x * x) == x);
+}
+
+test "@fabs" {
+ try testFabs(f128, 12.0);
+ comptime try testFabs(f128, 12.0);
+ try testFabs(f64, 12.0);
+ comptime try testFabs(f64, 12.0);
+ try testFabs(f32, 12.0);
+ comptime try testFabs(f32, 12.0);
+ try testFabs(f16, 12.0);
+ comptime try testFabs(f16, 12.0);
+
+ const x = 14.0;
+ const y = -x;
+ const z = @fabs(y);
+ comptime try expectEqual(x, z);
+}
+
+fn testFabs(comptime T: type, x: T) !void {
+ const y = -x;
+ const z = @fabs(y);
+ try expectEqual(x, z);
+}
+
+test "@floor" {
+ // FIXME: Generates a floorl function call
+ // testFloor(f128, 12.0);
+ comptime try testFloor(f128, 12.0);
+ try testFloor(f64, 12.0);
+ comptime try testFloor(f64, 12.0);
+ try testFloor(f32, 12.0);
+ comptime try testFloor(f32, 12.0);
+ try testFloor(f16, 12.0);
+ comptime try testFloor(f16, 12.0);
+
+ const x = 14.0;
+ const y = x + 0.7;
+ const z = @floor(y);
+ comptime try expectEqual(x, z);
+}
+
+fn testFloor(comptime T: type, x: T) !void {
+ const y = x + 0.6;
+ const z = @floor(y);
+ try expectEqual(x, z);
+}
+
+test "@ceil" {
+ // FIXME: Generates a ceill function call
+ //testCeil(f128, 12.0);
+ comptime try testCeil(f128, 12.0);
+ try testCeil(f64, 12.0);
+ comptime try testCeil(f64, 12.0);
+ try testCeil(f32, 12.0);
+ comptime try testCeil(f32, 12.0);
+ try testCeil(f16, 12.0);
+ comptime try testCeil(f16, 12.0);
+
+ const x = 14.0;
+ const y = x - 0.7;
+ const z = @ceil(y);
+ comptime try expectEqual(x, z);
+}
+
+fn testCeil(comptime T: type, x: T) !void {
+ const y = x - 0.8;
+ const z = @ceil(y);
+ try expectEqual(x, z);
+}
+
+test "@trunc" {
+ // FIXME: Generates a truncl function call
+ //testTrunc(f128, 12.0);
+ comptime try testTrunc(f128, 12.0);
+ try testTrunc(f64, 12.0);
+ comptime try testTrunc(f64, 12.0);
+ try testTrunc(f32, 12.0);
+ comptime try testTrunc(f32, 12.0);
+ try testTrunc(f16, 12.0);
+ comptime try testTrunc(f16, 12.0);
+
+ const x = 14.0;
+ const y = x + 0.7;
+ const z = @trunc(y);
+ comptime try expectEqual(x, z);
+}
+
+fn testTrunc(comptime T: type, x: T) !void {
+ {
+ const y = x + 0.8;
+ const z = @trunc(y);
+ try expectEqual(x, z);
+ }
+
+ {
+ const y = -x - 0.8;
+ const z = @trunc(y);
+ try expectEqual(-x, z);
+ }
+}
+
+test "@round" {
+ // FIXME: Generates a roundl function call
+ //testRound(f128, 12.0);
+ comptime try testRound(f128, 12.0);
+ try testRound(f64, 12.0);
+ comptime try testRound(f64, 12.0);
+ try testRound(f32, 12.0);
+ comptime try testRound(f32, 12.0);
+ try testRound(f16, 12.0);
+ comptime try testRound(f16, 12.0);
+
+ const x = 14.0;
+ const y = x + 0.4;
+ const z = @round(y);
+ comptime try expectEqual(x, z);
+}
+
+fn testRound(comptime T: type, x: T) !void {
+ const y = x - 0.5;
+ const z = @round(y);
+ try expectEqual(x, z);
+}
+
+test "comptime_int param and return" {
+ const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702);
+ try expect(a == 137114567242441932203689521744947848950);
+
+ const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768);
+ try expect(b == 985095453608931032642182098849559179469148836107390954364380);
+}
+
+fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int {
+ return a + b;
+}
+
+test "vector integer addition" {
+ const S = struct {
+ fn doTheTest() !void {
+ var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 };
+ var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 };
+ var result = a + b;
+ var result_array: [4]i32 = result;
+ const expected = [_]i32{ 6, 8, 10, 12 };
+ try expectEqualSlices(i32, &expected, &result_array);
+ }
+ };
+ try S.doTheTest();
+ comptime try S.doTheTest();
+}
+
+test "NaN comparison" {
+ try testNanEqNan(f16);
+ try testNanEqNan(f32);
+ try testNanEqNan(f64);
+ try testNanEqNan(f128);
+ comptime try testNanEqNan(f16);
+ comptime try testNanEqNan(f32);
+ comptime try testNanEqNan(f64);
+ comptime try testNanEqNan(f128);
+}
+
+fn testNanEqNan(comptime F: type) !void {
+ var nan1 = std.math.nan(F);
+ var nan2 = std.math.nan(F);
+ try expect(nan1 != nan2);
+ try expect(!(nan1 == nan2));
+ try expect(!(nan1 > nan2));
+ try expect(!(nan1 >= nan2));
+ try expect(!(nan1 < nan2));
+ try expect(!(nan1 <= nan2));
+}
+
+test "128-bit multiplication" {
+ var a: i128 = 3;
+ var b: i128 = 2;
+ var c = a * b;
+ try expect(c == 6);
+}
+
+test "vector comparison" {
+ const S = struct {
+ fn doTheTest() !void {
+ var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 };
+ var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 };
+ try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false }));
+ try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false }));
+ try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false }));
+ try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true }));
+ try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true }));
+ try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true }));
+ }
+ };
+ try S.doTheTest();
+ comptime try S.doTheTest();
+}
+
+test "compare undefined literal with comptime_int" {
+ var x = undefined == 1;
+ // x is now undefined with type bool
+ x = true;
+ try expect(x);
+}
+
+test "signed zeros are represented properly" {
+ const S = struct {
+ fn doTheTest() !void {
+ inline for ([_]type{ f16, f32, f64, f128 }) |T| {
+ const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);
+ var as_fp_val = -@as(T, 0.0);
+ var as_uint_val = @bitCast(ST, as_fp_val);
+ // Ensure the sign bit is set.
+ try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1);
+ }
+ }
+ };
+
+ try S.doTheTest();
+ comptime try S.doTheTest();
+}
From 29f41896ed9d99e82a88f4b63efa182ca0d2f93c Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Thu, 2 Sep 2021 13:50:24 -0700
Subject: [PATCH 129/160] sat-arithmetic: add operator support
- adds initial support for the operators +|, -|, *|, <<|, +|=, -|=, *|=, <<|=
- uses operators in addition to builtins in behavior test
- adds binOpExt() and assignBinOpExt() to AstGen.zig. these need to be audited
---
lib/std/zig/Ast.zig | 32 ++++++
lib/std/zig/parse.zig | 8 ++
lib/std/zig/render.zig | 8 ++
lib/std/zig/tokenizer.zig | 79 +++++++++++++++
src/Air.zig | 22 +++++
src/AstGen.zig | 124 +++++++++++++++++++++++-
src/Liveness.zig | 4 +
src/codegen.zig | 12 +++
src/codegen/c.zig | 3 +
src/codegen/llvm.zig | 66 +++++++++----
src/codegen/llvm/bindings.zig | 24 +++++
src/print_air.zig | 4 +
src/stage1/all_types.hpp | 16 ++-
src/stage1/astgen.cpp | 24 ++++-
src/stage1/codegen.cpp | 8 +-
src/stage1/ir.cpp | 24 ++---
src/stage1/ir_print.cpp | 8 +-
src/stage1/parser.cpp | 16 +++
src/stage1/tokenizer.cpp | 85 ++++++++++++++++
src/stage1/tokenizer.hpp | 8 ++
test/behavior/saturating_arithmetic.zig | 35 +++++--
21 files changed, 556 insertions(+), 54 deletions(-)
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index 5838dcd37a..3632551d17 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -396,6 +396,7 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_add,
.assign_sub,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_and,
.assign_bit_xor,
@@ -403,6 +404,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mul_wrap,
.assign_add_wrap,
.assign_sub_wrap,
+ .assign_mul_sat,
+ .assign_add_sat,
+ .assign_sub_sat,
.assign,
.merge_error_sets,
.mul,
@@ -410,12 +414,16 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.mod,
.array_mult,
.mul_wrap,
+ .mul_sat,
.add,
.sub,
.array_cat,
.add_wrap,
.sub_wrap,
+ .add_sat,
+ .sub_sat,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_and,
.bit_xor,
@@ -652,6 +660,7 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_add,
.assign_sub,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_and,
.assign_bit_xor,
@@ -659,6 +668,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mul_wrap,
.assign_add_wrap,
.assign_sub_wrap,
+ .assign_mul_sat,
+ .assign_add_sat,
+ .assign_sub_sat,
.assign,
.merge_error_sets,
.mul,
@@ -666,12 +678,16 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.mod,
.array_mult,
.mul_wrap,
+ .mul_sat,
.add,
.sub,
.array_cat,
.add_wrap,
.sub_wrap,
+ .add_sat,
+ .sub_sat,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_and,
.bit_xor,
@@ -2525,6 +2541,8 @@ pub const Node = struct {
assign_sub,
/// `lhs <<= rhs`. main_token is op.
assign_bit_shift_left,
+ /// `lhs <<|= rhs`. main_token is op.
+ assign_bit_shift_left_sat,
/// `lhs >>= rhs`. main_token is op.
assign_bit_shift_right,
/// `lhs &= rhs`. main_token is op.
@@ -2539,6 +2557,12 @@ pub const Node = struct {
assign_add_wrap,
/// `lhs -%= rhs`. main_token is op.
assign_sub_wrap,
+ /// `lhs *|= rhs`. main_token is op.
+ assign_mul_sat,
+ /// `lhs +|= rhs`. main_token is op.
+ assign_add_sat,
+ /// `lhs -|= rhs`. main_token is op.
+ assign_sub_sat,
/// `lhs = rhs`. main_token is op.
assign,
/// `lhs || rhs`. main_token is the `||`.
@@ -2553,6 +2577,8 @@ pub const Node = struct {
array_mult,
/// `lhs *% rhs`. main_token is the `*%`.
mul_wrap,
+ /// `lhs *| rhs`. main_token is the `*%`.
+ mul_sat,
/// `lhs + rhs`. main_token is the `+`.
add,
/// `lhs - rhs`. main_token is the `-`.
@@ -2563,8 +2589,14 @@ pub const Node = struct {
add_wrap,
/// `lhs -% rhs`. main_token is the `-%`.
sub_wrap,
+ /// `lhs +| rhs`. main_token is the `+|`.
+ add_sat,
+ /// `lhs -| rhs`. main_token is the `-|`.
+ sub_sat,
/// `lhs << rhs`. main_token is the `<<`.
bit_shift_left,
+ /// `lhs <<| rhs`. main_token is the `<<|`.
+ bit_shift_left_sat,
/// `lhs >> rhs`. main_token is the `>>`.
bit_shift_right,
/// `lhs & rhs`. main_token is the `&`.
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
index f7697027a3..a2780b5225 100644
--- a/lib/std/zig/parse.zig
+++ b/lib/std/zig/parse.zig
@@ -1269,6 +1269,7 @@ const Parser = struct {
.plus_equal => .assign_add,
.minus_equal => .assign_sub,
.angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left,
+ .angle_bracket_angle_bracket_left_pipe_equal => .assign_bit_shift_left_sat,
.angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right,
.ampersand_equal => .assign_bit_and,
.caret_equal => .assign_bit_xor,
@@ -1276,6 +1277,9 @@ const Parser = struct {
.asterisk_percent_equal => .assign_mul_wrap,
.plus_percent_equal => .assign_add_wrap,
.minus_percent_equal => .assign_sub_wrap,
+ .asterisk_pipe_equal => .assign_mul_sat,
+ .plus_pipe_equal => .assign_add_sat,
+ .minus_pipe_equal => .assign_sub_sat,
.equal => .assign,
else => return expr,
};
@@ -1343,6 +1347,7 @@ const Parser = struct {
.keyword_catch = .{ .prec = 40, .tag = .@"catch" },
.angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .bit_shift_left },
+ .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .bit_shift_left_sat },
.angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .bit_shift_right },
.plus = .{ .prec = 60, .tag = .add },
@@ -1350,6 +1355,8 @@ const Parser = struct {
.plus_plus = .{ .prec = 60, .tag = .array_cat },
.plus_percent = .{ .prec = 60, .tag = .add_wrap },
.minus_percent = .{ .prec = 60, .tag = .sub_wrap },
+ .plus_pipe = .{ .prec = 60, .tag = .add_sat },
+ .minus_pipe = .{ .prec = 60, .tag = .sub_sat },
.pipe_pipe = .{ .prec = 70, .tag = .merge_error_sets },
.asterisk = .{ .prec = 70, .tag = .mul },
@@ -1357,6 +1364,7 @@ const Parser = struct {
.percent = .{ .prec = 70, .tag = .mod },
.asterisk_asterisk = .{ .prec = 70, .tag = .array_mult },
.asterisk_percent = .{ .prec = 70, .tag = .mul_wrap },
+ .asterisk_pipe = .{ .prec = 70, .tag = .mul_sat },
});
fn parseExprPrecedence(p: *Parser, min_prec: i32) Error!Node.Index {
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
index 3029d38cb9..47f019d1cf 100644
--- a/lib/std/zig/render.zig
+++ b/lib/std/zig/render.zig
@@ -333,26 +333,32 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -367,8 +373,10 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.sub,
.sub_wrap,
+ .sub_sat,
.@"orelse",
=> {
const infix = datas[node];
diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig
index 3ef6c9a6ba..6afe7750d3 100644
--- a/lib/std/zig/tokenizer.zig
+++ b/lib/std/zig/tokenizer.zig
@@ -103,15 +103,21 @@ pub const Token = struct {
plus_equal,
plus_percent,
plus_percent_equal,
+ plus_pipe,
+ plus_pipe_equal,
minus,
minus_equal,
minus_percent,
minus_percent_equal,
+ minus_pipe,
+ minus_pipe_equal,
asterisk,
asterisk_equal,
asterisk_asterisk,
asterisk_percent,
asterisk_percent_equal,
+ asterisk_pipe,
+ asterisk_pipe_equal,
arrow,
colon,
slash,
@@ -124,6 +130,8 @@ pub const Token = struct {
angle_bracket_left_equal,
angle_bracket_angle_bracket_left,
angle_bracket_angle_bracket_left_equal,
+ angle_bracket_angle_bracket_left_pipe,
+ angle_bracket_angle_bracket_left_pipe_equal,
angle_bracket_right,
angle_bracket_right_equal,
angle_bracket_angle_bracket_right,
@@ -227,15 +235,21 @@ pub const Token = struct {
.plus_equal => "+=",
.plus_percent => "+%",
.plus_percent_equal => "+%=",
+ .plus_pipe => "+|",
+ .plus_pipe_equal => "+|=",
.minus => "-",
.minus_equal => "-=",
.minus_percent => "-%",
.minus_percent_equal => "-%=",
+ .minus_pipe => "-|",
+ .minus_pipe_equal => "-|=",
.asterisk => "*",
.asterisk_equal => "*=",
.asterisk_asterisk => "**",
.asterisk_percent => "*%",
.asterisk_percent_equal => "*%=",
+ .asterisk_pipe => "*|",
+ .asterisk_pipe_equal => "*|=",
.arrow => "->",
.colon => ":",
.slash => "/",
@@ -248,6 +262,8 @@ pub const Token = struct {
.angle_bracket_left_equal => "<=",
.angle_bracket_angle_bracket_left => "<<",
.angle_bracket_angle_bracket_left_equal => "<<=",
+ .angle_bracket_angle_bracket_left_pipe => "<<|",
+ .angle_bracket_angle_bracket_left_pipe_equal => "<<|=",
.angle_bracket_right => ">",
.angle_bracket_right_equal => ">=",
.angle_bracket_angle_bracket_right => ">>",
@@ -352,8 +368,10 @@ pub const Tokenizer = struct {
pipe,
minus,
minus_percent,
+ minus_pipe,
asterisk,
asterisk_percent,
+ asterisk_pipe,
slash,
line_comment_start,
line_comment,
@@ -382,8 +400,10 @@ pub const Tokenizer = struct {
percent,
plus,
plus_percent,
+ plus_pipe,
angle_bracket_left,
angle_bracket_angle_bracket_left,
+ angle_bracket_angle_bracket_left_pipe,
angle_bracket_right,
angle_bracket_angle_bracket_right,
period,
@@ -584,6 +604,9 @@ pub const Tokenizer = struct {
'%' => {
state = .asterisk_percent;
},
+ '|' => {
+ state = .asterisk_pipe;
+ },
else => {
result.tag = .asterisk;
break;
@@ -602,6 +625,18 @@ pub const Tokenizer = struct {
},
},
+ .asterisk_pipe => switch (c) {
+ '=' => {
+ result.tag = .asterisk_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .asterisk_pipe;
+ break;
+ },
+ },
+
.percent => switch (c) {
'=' => {
result.tag = .percent_equal;
@@ -628,6 +663,9 @@ pub const Tokenizer = struct {
'%' => {
state = .plus_percent;
},
+ '|' => {
+ state = .plus_pipe;
+ },
else => {
result.tag = .plus;
break;
@@ -646,6 +684,18 @@ pub const Tokenizer = struct {
},
},
+ .plus_pipe => switch (c) {
+ '=' => {
+ result.tag = .plus_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .plus_pipe;
+ break;
+ },
+ },
+
.caret => switch (c) {
'=' => {
result.tag = .caret_equal;
@@ -903,6 +953,9 @@ pub const Tokenizer = struct {
'%' => {
state = .minus_percent;
},
+ '|' => {
+ state = .minus_pipe;
+ },
else => {
result.tag = .minus;
break;
@@ -920,6 +973,17 @@ pub const Tokenizer = struct {
break;
},
},
+ .minus_pipe => switch (c) {
+ '=' => {
+ result.tag = .minus_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .minus_pipe;
+ break;
+ },
+ },
.angle_bracket_left => switch (c) {
'<' => {
@@ -942,12 +1006,27 @@ pub const Tokenizer = struct {
self.index += 1;
break;
},
+ '|' => {
+ result.tag = .angle_bracket_angle_bracket_left_pipe;
+ },
else => {
result.tag = .angle_bracket_angle_bracket_left;
break;
},
},
+ .angle_bracket_angle_bracket_left_pipe => switch (c) {
+ '=' => {
+ result.tag = .angle_bracket_angle_bracket_left_pipe_equal;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.tag = .angle_bracket_angle_bracket_left_pipe;
+ break;
+ },
+ },
+
.angle_bracket_right => switch (c) {
'>' => {
state = .angle_bracket_angle_bracket_right;
diff --git a/src/Air.zig b/src/Air.zig
index b5d19127a0..b7d3938352 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -44,6 +44,11 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
addwrap,
+ /// Saturating integer addition.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ addsat,
/// Float or integer subtraction. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -54,6 +59,11 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
subwrap,
+ /// Saturating integer subtraction.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ subsat,
/// Float or integer multiplication. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -64,6 +74,11 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
mulwrap,
+ /// Saturating integer multiplication.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ mulsat,
/// Integer or float division. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -110,6 +125,9 @@ pub const Inst = struct {
/// Shift left. `<<`
/// Uses the `bin_op` field.
shl,
+ /// Shift left saturating. `<<|`
+ /// Uses the `bin_op` field.
+ shl_sat,
/// Bitwise XOR. `^`
/// Uses the `bin_op` field.
xor,
@@ -568,10 +586,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.add,
.addwrap,
+ .addsat,
.sub,
.subwrap,
+ .subsat,
.mul,
.mulwrap,
+ .mulsat,
.div,
.rem,
.mod,
@@ -582,6 +603,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.ptr_sub,
.shr,
.shl,
+ .shl_sat,
=> return air.typeOf(datas[inst].bin_op.lhs),
.cmp_lt,
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 15594ac27c..b3af3eb86b 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -318,27 +318,35 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.add,
.add_wrap,
+ .add_sat,
.sub,
.sub_wrap,
+ .sub_sat,
.mul,
.mul_wrap,
+ .mul_sat,
.div,
.mod,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bang_equal,
@@ -526,6 +534,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignShift(gz, scope, node, .shl);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_bit_shift_left_sat => {
+ try assignBinOpExt(gz, scope, node, .shl_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
.assign_bit_shift_right => {
try assignShift(gz, scope, node, .shr);
return rvalue(gz, rl, .void_value, node);
@@ -555,6 +567,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignOp(gz, scope, node, .subwrap);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_sub_sat => {
+ try assignBinOpExt(gz, scope, node, .sub_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
.assign_mod => {
try assignOp(gz, scope, node, .mod_rem);
return rvalue(gz, rl, .void_value, node);
@@ -567,6 +583,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignOp(gz, scope, node, .addwrap);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_add_sat => {
+ try assignBinOpExt(gz, scope, node, .add_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
.assign_mul => {
try assignOp(gz, scope, node, .mul);
return rvalue(gz, rl, .void_value, node);
@@ -575,17 +595,25 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
try assignOp(gz, scope, node, .mulwrap);
return rvalue(gz, rl, .void_value, node);
},
+ .assign_mul_sat => {
+ try assignBinOpExt(gz, scope, node, .mul_with_saturation);
+ return rvalue(gz, rl, .void_value, node);
+ },
// zig fmt: off
- .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
+ .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
+ .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation),
+ .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
+ .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
+ .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
+ .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
.bit_and => {
@@ -2685,6 +2713,31 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
+// TODO: is there an existing method to accomplish this?
+// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general
+fn assignBinOpExt(
+ gz: *GenZir,
+ scope: *Scope,
+ infix_node: Ast.Node.Index,
+ op_inst_tag: Zir.Inst.Extended,
+) InnerError!void {
+ try emitDbgNode(gz, infix_node);
+ const astgen = gz.astgen;
+ const tree = astgen.tree;
+ const node_datas = tree.nodes.items(.data);
+
+ const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
+ const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
+ const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
+ const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
+ const result = try gz.addExtendedPayload(op_inst_tag, Zir.Inst.BinNode{
+ .node = gz.nodeIndexToRelative(infix_node),
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ _ = try gz.addBin(.store, lhs_ptr, result);
+}
+
fn assignShift(
gz: *GenZir,
scope: *Scope,
@@ -2708,6 +2761,29 @@ fn assignShift(
_ = try gz.addBin(.store, lhs_ptr, result);
}
+fn assignShiftSat(
+ gz: *GenZir,
+ scope: *Scope,
+ infix_node: ast.Node.Index,
+ op_inst_tag: Zir.Inst.Tag,
+) InnerError!void {
+ try emitDbgNode(gz, infix_node);
+ const astgen = gz.astgen;
+ const tree = astgen.tree;
+ const node_datas = tree.nodes.items(.data);
+
+ const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
+ const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
+ const rhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
+ const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
+
+ const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ _ = try gz.addBin(.store, lhs_ptr, result);
+}
+
fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const tree = astgen.tree;
@@ -7827,6 +7903,26 @@ fn shiftOp(
return rvalue(gz, rl, result, node);
}
+// TODO: is there an existing way to do this?
+// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
+fn binOpExt(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ node: Ast.Node.Index,
+ lhs_node: Ast.Node.Index,
+ rhs_node: Ast.Node.Index,
+ tag: Zir.Inst.Extended,
+) InnerError!Zir.Inst.Ref {
+ const lhs = try expr(gz, scope, .none, lhs_node);
+ const rhs = try expr(gz, scope, .none, rhs_node);
+ const result = try gz.addExtendedPayload(tag, Zir.Inst.Bin{
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ return rvalue(gz, rl, result, node);
+}
+
fn cImport(
gz: *GenZir,
scope: *Scope,
@@ -8119,26 +8215,32 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.asm_simple,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -8154,10 +8256,12 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.switch_range,
.field_access,
.sub,
.sub_wrap,
+ .sub_sat,
.slice,
.slice_open,
.slice_sentinel,
@@ -8352,26 +8456,32 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.tagged_union_enum_tag_trailing,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -8387,9 +8497,11 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.switch_range,
.sub,
.sub_wrap,
+ .sub_sat,
.slice,
.slice_open,
.slice_sentinel,
@@ -8524,26 +8636,32 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.asm_simple,
.add,
.add_wrap,
+ .add_sat,
.array_cat,
.array_mult,
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_bit_shift_left,
+ .assign_bit_shift_left_sat,
.assign_bit_shift_right,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
+ .assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
+ .assign_add_sat,
.assign_mul,
.assign_mul_wrap,
+ .assign_mul_sat,
.bang_equal,
.bit_and,
.bit_or,
.bit_shift_left,
+ .bit_shift_left_sat,
.bit_shift_right,
.bit_xor,
.bool_and,
@@ -8559,10 +8677,12 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.mod,
.mul,
.mul_wrap,
+ .mul_sat,
.switch_range,
.field_access,
.sub,
.sub_wrap,
+ .sub_sat,
.slice,
.slice_open,
.slice_sentinel,
diff --git a/src/Liveness.zig b/src/Liveness.zig
index 25dd29b0f6..c34153b76f 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -226,10 +226,13 @@ fn analyzeInst(
switch (inst_tags[inst]) {
.add,
.addwrap,
+ .addsat,
.sub,
.subwrap,
+ .subsat,
.mul,
.mulwrap,
+ .mulsat,
.div,
.rem,
.mod,
@@ -252,6 +255,7 @@ fn analyzeInst(
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_sat,
.shr,
.atomic_store_unordered,
.atomic_store_monotonic,
diff --git a/src/codegen.zig b/src/codegen.zig
index 7c359e90c0..a1f812388f 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -826,10 +826,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
// zig fmt: off
.add, .ptr_add => try self.airAdd(inst),
.addwrap => try self.airAddWrap(inst),
+ .addsat => try self.airArithmeticOpSat(inst, "addsat"),
.sub, .ptr_sub => try self.airSub(inst),
.subwrap => try self.airSubWrap(inst),
+ .subsat => try self.airArithmeticOpSat(inst, "subsat"),
.mul => try self.airMul(inst),
.mulwrap => try self.airMulWrap(inst),
+ .mulsat => try self.airArithmeticOpSat(inst, "mulsat"),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
@@ -848,6 +851,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.xor => try self.airXor(inst),
.shr => try self.airShr(inst),
.shl => try self.airShl(inst),
+ .shl_sat => try self.airArithmeticOpSat(inst, "shl_sat"),
.alloc => try self.airAlloc(inst),
.arg => try self.airArg(inst),
@@ -1320,6 +1324,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airArithmeticOpSat(self: *Self, inst: Air.Inst.Index, comptime name: []const u8) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement " ++ name ++ " for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airMul(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 4964f17cd3..dce0c10b4c 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -885,14 +885,17 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
// that wrapping is UB.
.add, .ptr_add => try airBinOp( f, inst, " + "),
.addwrap => try airWrapOp(f, inst, " + ", "addw_"),
+ .addsat => return o.dg.fail("TODO: C backend: implement codegen for addsat", .{}),
// TODO use a different strategy for sub that communicates to the optimizer
// that wrapping is UB.
.sub, .ptr_sub => try airBinOp( f, inst, " - "),
.subwrap => try airWrapOp(f, inst, " - ", "subw_"),
+ .subsat => return o.dg.fail("TODO: C backend: implement codegen for subsat", .{}),
// TODO use a different strategy for mul that communicates to the optimizer
// that wrapping is UB.
.mul => try airBinOp( f, inst, " * "),
.mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
+ .mulsat => return o.dg.fail("TODO: C backend: implement codegen for mulsat", .{}),
// TODO use a different strategy for div that communicates to the optimizer
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index f65e0f6d72..c21985d1c5 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1236,12 +1236,15 @@ pub const FuncGen = struct {
for (body) |inst| {
const opt_value: ?*const llvm.Value = switch (air_tags[inst]) {
// zig fmt: off
- .add => try self.airAdd(inst, false),
- .addwrap => try self.airAdd(inst, true),
- .sub => try self.airSub(inst, false),
- .subwrap => try self.airSub(inst, true),
- .mul => try self.airMul(inst, false),
- .mulwrap => try self.airMul(inst, true),
+ .add => try self.airAdd(inst, .standard),
+ .addwrap => try self.airAdd(inst, .wrapping),
+ .addsat => try self.airAdd(inst, .saturated),
+ .sub => try self.airSub(inst, .standard),
+ .subwrap => try self.airSub(inst, .wrapping),
+ .subsat => try self.airSub(inst, .saturated),
+ .mul => try self.airMul(inst, .standard),
+ .mulwrap => try self.airMul(inst, .wrapping),
+ .mulsat => try self.airMul(inst, .saturated),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
@@ -1252,7 +1255,8 @@ pub const FuncGen = struct {
.bit_or, .bool_or => try self.airOr(inst),
.xor => try self.airXor(inst),
- .shl => try self.airShl(inst),
+ .shl => try self.airShl(inst, false),
+ .shl_sat => try self.airShl(inst, true),
.shr => try self.airShr(inst),
.cmp_eq => try self.airCmp(inst, .eq),
@@ -2024,7 +2028,8 @@ pub const FuncGen = struct {
return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
}
- fn airAdd(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
+ const ArithmeticType = enum { standard, wrapping, saturated };
+ fn airAdd(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2033,13 +2038,20 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isRuntimeFloat()) return self.builder.buildFAdd(lhs, rhs, "");
- if (wrap) return self.builder.buildAdd(lhs, rhs, "");
+ if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, "");
+ if (ty == .wrapping)
+ return self.builder.buildAdd(lhs, rhs, "")
+ else if (ty == .saturated) {
+ if (inst_ty.isSignedInt())
+ return self.builder.buildSAddSat(lhs, rhs, "")
+ else
+ return self.builder.buildUAddSat(lhs, rhs, "");
+ }
if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, "");
return self.builder.buildNUWAdd(lhs, rhs, "");
}
- fn airSub(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
+ fn airSub(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2048,13 +2060,20 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isRuntimeFloat()) return self.builder.buildFSub(lhs, rhs, "");
- if (wrap) return self.builder.buildSub(lhs, rhs, "");
+ if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, "");
+ if (ty == .wrapping)
+ return self.builder.buildSub(lhs, rhs, "")
+ else if (ty == .saturated) {
+ if (inst_ty.isSignedInt())
+ return self.builder.buildSSubSat(lhs, rhs, "")
+ else
+ return self.builder.buildUSubSat(lhs, rhs, "");
+ }
if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, "");
return self.builder.buildNUWSub(lhs, rhs, "");
}
- fn airMul(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value {
+ fn airMul(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2063,8 +2082,15 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isRuntimeFloat()) return self.builder.buildFMul(lhs, rhs, "");
- if (wrap) return self.builder.buildMul(lhs, rhs, "");
+ if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, "");
+ if (ty == .wrapping)
+ return self.builder.buildMul(lhs, rhs, "")
+ else if (ty == .saturated) {
+ if (inst_ty.isSignedInt())
+ return self.builder.buildSMulFixSat(lhs, rhs, "")
+ else
+ return self.builder.buildUMulFixSat(lhs, rhs, "");
+ }
if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, "");
return self.builder.buildNUWMul(lhs, rhs, "");
}
@@ -2174,7 +2200,7 @@ pub const FuncGen = struct {
return self.builder.buildXor(lhs, rhs, "");
}
- fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ fn airShl(self: *FuncGen, inst: Air.Inst.Index, sat: bool) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
@@ -2186,6 +2212,12 @@ pub const FuncGen = struct {
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
+ if (sat) {
+ return if (lhs_type.isSignedInt())
+ self.builder.buildSShlSat(lhs, casted_rhs, "")
+ else
+ self.builder.buildUShlSat(lhs, casted_rhs, "");
+ }
return self.builder.buildShl(lhs, casted_rhs, "");
}
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index 9d32682260..178c381235 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -397,6 +397,12 @@ pub const Builder = opaque {
pub const buildNUWAdd = LLVMBuildNUWAdd;
extern fn LLVMBuildNUWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSAddSat = ZigLLVMBuildSAddSat;
+ extern fn ZigLLVMBuildSAddSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUAddSat = ZigLLVMBuildUAddSat;
+ extern fn ZigLLVMBuildUAddSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildFSub = LLVMBuildFSub;
extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
@@ -409,6 +415,12 @@ pub const Builder = opaque {
pub const buildNUWSub = LLVMBuildNUWSub;
extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSSubSat = ZigLLVMBuildSSubSat;
+ extern fn ZigLLVMBuildSSubSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUSubSat = ZigLLVMBuildUSubSat;
+ extern fn ZigLLVMBuildUSubSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildFMul = LLVMBuildFMul;
extern fn LLVMBuildFMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
@@ -421,6 +433,12 @@ pub const Builder = opaque {
pub const buildNUWMul = LLVMBuildNUWMul;
extern fn LLVMBuildNUWMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSMulFixSat = ZigLLVMBuildSMulFixSat;
+ extern fn ZigLLVMBuildSMulFixSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUMulFixSat = ZigLLVMBuildUMulFixSat;
+ extern fn ZigLLVMBuildUMulFixSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildUDiv = LLVMBuildUDiv;
extern fn LLVMBuildUDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
@@ -451,6 +469,12 @@ pub const Builder = opaque {
pub const buildShl = LLVMBuildShl;
extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildSShlSat = ZigLLVMBuildSShlSat;
+ extern fn ZigLLVMBuildSShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildUShlSat = ZigLLVMBuildUShlSat;
+ extern fn ZigLLVMBuildUShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildOr = LLVMBuildOr;
extern fn LLVMBuildOr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
diff --git a/src/print_air.zig b/src/print_air.zig
index 90df06760b..7d178b52f3 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -104,10 +104,13 @@ const Writer = struct {
.add,
.addwrap,
+ .addsat,
.sub,
.subwrap,
+ .subsat,
.mul,
.mulwrap,
+ .mulsat,
.div,
.rem,
.mod,
@@ -130,6 +133,7 @@ const Writer = struct {
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_sat,
.shr,
.set_union_tag,
=> try w.writeBinOp(s, inst),
diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp
index 13c37fc839..e31a7015b0 100644
--- a/src/stage1/all_types.hpp
+++ b/src/stage1/all_types.hpp
@@ -812,14 +812,18 @@ enum BinOpType {
BinOpTypeInvalid,
BinOpTypeAssign,
BinOpTypeAssignTimes,
+ BinOpTypeAssignTimesSat,
BinOpTypeAssignTimesWrap,
BinOpTypeAssignDiv,
BinOpTypeAssignMod,
BinOpTypeAssignPlus,
+ BinOpTypeAssignPlusSat,
BinOpTypeAssignPlusWrap,
BinOpTypeAssignMinus,
+ BinOpTypeAssignMinusSat,
BinOpTypeAssignMinusWrap,
BinOpTypeAssignBitShiftLeft,
+ BinOpTypeAssignBitShiftLeftSat,
BinOpTypeAssignBitShiftRight,
BinOpTypeAssignBitAnd,
BinOpTypeAssignBitXor,
@@ -836,12 +840,16 @@ enum BinOpType {
BinOpTypeBinXor,
BinOpTypeBinAnd,
BinOpTypeBitShiftLeft,
+ BinOpTypeBitShiftLeftSat,
BinOpTypeBitShiftRight,
BinOpTypeAdd,
+ BinOpTypeAddSat,
BinOpTypeAddWrap,
BinOpTypeSub,
+ BinOpTypeSubSat,
BinOpTypeSubWrap,
BinOpTypeMult,
+ BinOpTypeMultSat,
BinOpTypeMultWrap,
BinOpTypeDiv,
BinOpTypeMod,
@@ -2958,10 +2966,10 @@ enum IrBinOp {
IrBinOpArrayMult,
IrBinOpMaximum,
IrBinOpMinimum,
- IrBinOpSatAdd,
- IrBinOpSatSub,
- IrBinOpSatMul,
- IrBinOpSatShl,
+ IrBinOpAddSat,
+ IrBinOpSubSat,
+ IrBinOpMultSat,
+ IrBinOpShlSat,
};
struct Stage1ZirInstBinOp {
diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp
index 9e5d9da9ee..14808dd0a2 100644
--- a/src/stage1/astgen.cpp
+++ b/src/stage1/astgen.cpp
@@ -3672,6 +3672,8 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMult), lval, result_loc);
case BinOpTypeAssignTimesWrap:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMultWrap), lval, result_loc);
+ case BinOpTypeAssignTimesSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMultSat), lval, result_loc);
case BinOpTypeAssignDiv:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpDivUnspecified), lval, result_loc);
case BinOpTypeAssignMod:
@@ -3680,12 +3682,18 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAdd), lval, result_loc);
case BinOpTypeAssignPlusWrap:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAddWrap), lval, result_loc);
+ case BinOpTypeAssignPlusSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAddSat), lval, result_loc);
case BinOpTypeAssignMinus:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSub), lval, result_loc);
case BinOpTypeAssignMinusWrap:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSubWrap), lval, result_loc);
+ case BinOpTypeAssignMinusSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSubSat), lval, result_loc);
case BinOpTypeAssignBitShiftLeft:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpBitShiftLeftLossy), lval, result_loc);
+ case BinOpTypeAssignBitShiftLeftSat:
+ return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpShlSat), lval, result_loc);
case BinOpTypeAssignBitShiftRight:
return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpBitShiftRightLossy), lval, result_loc);
case BinOpTypeAssignBitAnd:
@@ -3718,20 +3726,28 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBinAnd), lval, result_loc);
case BinOpTypeBitShiftLeft:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBitShiftLeftLossy), lval, result_loc);
+ case BinOpTypeBitShiftLeftSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpShlSat), lval, result_loc);
case BinOpTypeBitShiftRight:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBitShiftRightLossy), lval, result_loc);
case BinOpTypeAdd:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAdd), lval, result_loc);
case BinOpTypeAddWrap:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAddWrap), lval, result_loc);
+ case BinOpTypeAddSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAddSat), lval, result_loc);
case BinOpTypeSub:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSub), lval, result_loc);
case BinOpTypeSubWrap:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSubWrap), lval, result_loc);
+ case BinOpTypeSubSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSubSat), lval, result_loc);
case BinOpTypeMult:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMult), lval, result_loc);
case BinOpTypeMultWrap:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMultWrap), lval, result_loc);
+ case BinOpTypeMultSat:
+ return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMultSat), lval, result_loc);
case BinOpTypeDiv:
return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpDivUnspecified), lval, result_loc);
case BinOpTypeMod:
@@ -4716,7 +4732,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatAdd, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpAddSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdSatSub:
@@ -4731,7 +4747,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatSub, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSubSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdSatMul:
@@ -4746,7 +4762,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatMul, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMultSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdSatShl:
@@ -4761,7 +4777,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
if (arg1_value == ag->codegen->invalid_inst_src)
return arg1_value;
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatShl, arg0_value, arg1_value, true);
+ Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpShlSat, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
case BuiltinFnIdMemcpy:
diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp
index f84847a9fe..eade843354 100644
--- a/src/stage1/codegen.cpp
+++ b/src/stage1/codegen.cpp
@@ -3333,7 +3333,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatAdd:
+ case IrBinOpAddSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSAddSat(g->builder, op1_value, op2_value, "");
@@ -3343,7 +3343,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatSub:
+ case IrBinOpSubSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSSubSat(g->builder, op1_value, op2_value, "");
@@ -3353,7 +3353,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatMul:
+ case IrBinOpMultSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSMulFixSat(g->builder, op1_value, op2_value, "");
@@ -3363,7 +3363,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable,
} else {
zig_unreachable();
}
- case IrBinOpSatShl:
+ case IrBinOpShlSat:
if (scalar_type->id == ZigTypeIdInt) {
if (scalar_type->data.integral.is_signed) {
return ZigLLVMBuildSShlSat(g->builder, op1_value, op2_value, "");
diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp
index b853961beb..2f2cfe08f3 100644
--- a/src/stage1/ir.cpp
+++ b/src/stage1/ir.cpp
@@ -9820,28 +9820,28 @@ static ErrorMsg *ir_eval_math_op_scalar(IrAnalyze *ira, Scope *scope, AstNode *s
float_min(out_val, op1_val, op2_val);
}
break;
- case IrBinOpSatAdd:
+ case IrBinOpAddSat:
if (is_int) {
bigint_add_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
} else {
zig_unreachable();
}
break;
- case IrBinOpSatSub:
+ case IrBinOpSubSat:
if (is_int) {
bigint_sub_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
} else {
zig_unreachable();
}
break;
- case IrBinOpSatMul:
+ case IrBinOpMultSat:
if (is_int) {
bigint_mul_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
} else {
zig_unreachable();
}
break;
- case IrBinOpSatShl:
+ case IrBinOpShlSat:
if (is_int) {
bigint_shl_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed);
} else {
@@ -10069,10 +10069,10 @@ static bool ok_float_op(IrBinOp op) {
case IrBinOpBitShiftRightExact:
case IrBinOpAddWrap:
case IrBinOpSubWrap:
- case IrBinOpSatAdd:
- case IrBinOpSatSub:
- case IrBinOpSatMul:
- case IrBinOpSatShl:
+ case IrBinOpAddSat:
+ case IrBinOpSubSat:
+ case IrBinOpMultSat:
+ case IrBinOpShlSat:
case IrBinOpMultWrap:
case IrBinOpArrayCat:
case IrBinOpArrayMult:
@@ -11046,10 +11046,10 @@ static Stage1AirInst *ir_analyze_instruction_bin_op(IrAnalyze *ira, Stage1ZirIns
case IrBinOpRemMod:
case IrBinOpMaximum:
case IrBinOpMinimum:
- case IrBinOpSatAdd:
- case IrBinOpSatSub:
- case IrBinOpSatMul:
- case IrBinOpSatShl:
+ case IrBinOpAddSat:
+ case IrBinOpSubSat:
+ case IrBinOpMultSat:
+ case IrBinOpShlSat:
return ir_analyze_bin_op_math(ira, bin_op_instruction);
case IrBinOpArrayCat:
return ir_analyze_array_cat(ira, bin_op_instruction);
diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp
index a76d3e4d5a..f92f146d84 100644
--- a/src/stage1/ir_print.cpp
+++ b/src/stage1/ir_print.cpp
@@ -737,13 +737,13 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) {
return "@maximum";
case IrBinOpMinimum:
return "@minimum";
- case IrBinOpSatAdd:
+ case IrBinOpAddSat:
return "@addWithSaturation";
- case IrBinOpSatSub:
+ case IrBinOpSubSat:
return "@subWithSaturation";
- case IrBinOpSatMul:
+ case IrBinOpMultSat:
return "@mulWithSaturation";
- case IrBinOpSatShl:
+ case IrBinOpShlSat:
return "@shlWithSaturation";
}
zig_unreachable();
diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp
index f7061bb232..fdc0777aff 100644
--- a/src/stage1/parser.cpp
+++ b/src/stage1/parser.cpp
@@ -2381,6 +2381,7 @@ static AstNode *ast_parse_switch_item(ParseContext *pc) {
// / PLUSEQUAL
// / MINUSEQUAL
// / LARROW2EQUAL
+// / LARROW2PIPEEQUAL
// / RARROW2EQUAL
// / AMPERSANDEQUAL
// / CARETEQUAL
@@ -2388,6 +2389,9 @@ static AstNode *ast_parse_switch_item(ParseContext *pc) {
// / ASTERISKPERCENTEQUAL
// / PLUSPERCENTEQUAL
// / MINUSPERCENTEQUAL
+// / ASTERISKPIPEEQUAL
+// / PLUSPIPEEQUAL
+// / MINUSPIPEEQUAL
// / EQUAL
static AstNode *ast_parse_assign_op(ParseContext *pc) {
// In C, we have `T arr[N] = {[i] = T{}};` but it doesn't
@@ -2396,17 +2400,21 @@ static AstNode *ast_parse_assign_op(ParseContext *pc) {
table[TokenIdBitAndEq] = BinOpTypeAssignBitAnd;
table[TokenIdBitOrEq] = BinOpTypeAssignBitOr;
table[TokenIdBitShiftLeftEq] = BinOpTypeAssignBitShiftLeft;
+ table[TokenIdBitShiftLeftPipeEq] = BinOpTypeAssignBitShiftLeftSat;
table[TokenIdBitShiftRightEq] = BinOpTypeAssignBitShiftRight;
table[TokenIdBitXorEq] = BinOpTypeAssignBitXor;
table[TokenIdDivEq] = BinOpTypeAssignDiv;
table[TokenIdEq] = BinOpTypeAssign;
table[TokenIdMinusEq] = BinOpTypeAssignMinus;
table[TokenIdMinusPercentEq] = BinOpTypeAssignMinusWrap;
+ table[TokenIdMinusPipeEq] = BinOpTypeAssignMinusSat;
table[TokenIdModEq] = BinOpTypeAssignMod;
table[TokenIdPlusEq] = BinOpTypeAssignPlus;
table[TokenIdPlusPercentEq] = BinOpTypeAssignPlusWrap;
+ table[TokenIdPlusPipeEq] = BinOpTypeAssignPlusSat;
table[TokenIdTimesEq] = BinOpTypeAssignTimes;
table[TokenIdTimesPercentEq] = BinOpTypeAssignTimesWrap;
+ table[TokenIdTimesPipeEq] = BinOpTypeAssignTimesSat;
BinOpType op = table[pc->token_ids[pc->current_token]];
if (op != BinOpTypeInvalid) {
@@ -2483,10 +2491,12 @@ static AstNode *ast_parse_bitwise_op(ParseContext *pc) {
// BitShiftOp
// <- LARROW2
+// / LARROW2PIPE
// / RARROW2
static AstNode *ast_parse_bit_shift_op(ParseContext *pc) {
BinOpType table[TokenIdCount] = {};
table[TokenIdBitShiftLeft] = BinOpTypeBitShiftLeft;
+ table[TokenIdBitShiftLeftPipe] = BinOpTypeBitShiftLeftSat;
table[TokenIdBitShiftRight] = BinOpTypeBitShiftRight;
BinOpType op = table[pc->token_ids[pc->current_token]];
@@ -2506,6 +2516,8 @@ static AstNode *ast_parse_bit_shift_op(ParseContext *pc) {
// / PLUS2
// / PLUSPERCENT
// / MINUSPERCENT
+// / PLUSPIPE
+// / MINUSPIPE
static AstNode *ast_parse_addition_op(ParseContext *pc) {
BinOpType table[TokenIdCount] = {};
table[TokenIdPlus] = BinOpTypeAdd;
@@ -2513,6 +2525,8 @@ static AstNode *ast_parse_addition_op(ParseContext *pc) {
table[TokenIdPlusPlus] = BinOpTypeArrayCat;
table[TokenIdPlusPercent] = BinOpTypeAddWrap;
table[TokenIdMinusPercent] = BinOpTypeSubWrap;
+ table[TokenIdPlusPipe] = BinOpTypeAddSat;
+ table[TokenIdMinusPipe] = BinOpTypeSubSat;
BinOpType op = table[pc->token_ids[pc->current_token]];
if (op != BinOpTypeInvalid) {
@@ -2532,6 +2546,7 @@ static AstNode *ast_parse_addition_op(ParseContext *pc) {
// / PERCENT
// / ASTERISK2
// / ASTERISKPERCENT
+// / ASTERISKPIPE
static AstNode *ast_parse_multiply_op(ParseContext *pc) {
BinOpType table[TokenIdCount] = {};
table[TokenIdBarBar] = BinOpTypeMergeErrorSets;
@@ -2540,6 +2555,7 @@ static AstNode *ast_parse_multiply_op(ParseContext *pc) {
table[TokenIdPercent] = BinOpTypeMod;
table[TokenIdStarStar] = BinOpTypeArrayMult;
table[TokenIdTimesPercent] = BinOpTypeMultWrap;
+ table[TokenIdTimesPipe] = BinOpTypeMultSat;
BinOpType op = table[pc->token_ids[pc->current_token]];
if (op != BinOpTypeInvalid) {
diff --git a/src/stage1/tokenizer.cpp b/src/stage1/tokenizer.cpp
index f10579c966..3560193927 100644
--- a/src/stage1/tokenizer.cpp
+++ b/src/stage1/tokenizer.cpp
@@ -226,8 +226,10 @@ enum TokenizeState {
TokenizeState_pipe,
TokenizeState_minus,
TokenizeState_minus_percent,
+ TokenizeState_minus_pipe,
TokenizeState_asterisk,
TokenizeState_asterisk_percent,
+ TokenizeState_asterisk_pipe,
TokenizeState_slash,
TokenizeState_line_comment_start,
TokenizeState_line_comment,
@@ -257,8 +259,10 @@ enum TokenizeState {
TokenizeState_percent,
TokenizeState_plus,
TokenizeState_plus_percent,
+ TokenizeState_plus_pipe,
TokenizeState_angle_bracket_left,
TokenizeState_angle_bracket_angle_bracket_left,
+ TokenizeState_angle_bracket_angle_bracket_left_pipe,
TokenizeState_angle_bracket_right,
TokenizeState_angle_bracket_angle_bracket_right,
TokenizeState_period,
@@ -548,6 +552,9 @@ void tokenize(const char *source, Tokenization *out) {
case '%':
t.state = TokenizeState_asterisk_percent;
break;
+ case '|':
+ t.state = TokenizeState_asterisk_pipe;
+ break;
default:
t.state = TokenizeState_start;
continue;
@@ -568,6 +575,21 @@ void tokenize(const char *source, Tokenization *out) {
continue;
}
break;
+ case TokenizeState_asterisk_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdTimesPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdTimesPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdTimesPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_percent:
switch (c) {
case 0:
@@ -596,6 +618,9 @@ void tokenize(const char *source, Tokenization *out) {
case '%':
t.state = TokenizeState_plus_percent;
break;
+ case '|':
+ t.state = TokenizeState_plus_pipe;
+ break;
default:
t.state = TokenizeState_start;
continue;
@@ -616,6 +641,21 @@ void tokenize(const char *source, Tokenization *out) {
continue;
}
break;
+ case TokenizeState_plus_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdPlusPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdPlusPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdPlusPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_caret:
switch (c) {
case 0:
@@ -891,6 +931,9 @@ void tokenize(const char *source, Tokenization *out) {
case '%':
t.state = TokenizeState_minus_percent;
break;
+ case '|':
+ t.state = TokenizeState_minus_pipe;
+ break;
default:
t.state = TokenizeState_start;
continue;
@@ -911,6 +954,21 @@ void tokenize(const char *source, Tokenization *out) {
continue;
}
break;
+ case TokenizeState_minus_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdMinusPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdMinusPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdMinusPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_angle_bracket_left:
switch (c) {
case 0:
@@ -936,12 +994,31 @@ void tokenize(const char *source, Tokenization *out) {
t.out->ids.last() = TokenIdBitShiftLeftEq;
t.state = TokenizeState_start;
break;
+ case '|':
+ // t.out->ids.last() = TokenIdBitShiftLeftPipe;
+ t.state = TokenizeState_angle_bracket_angle_bracket_left_pipe;
+ break;
default:
t.out->ids.last() = TokenIdBitShiftLeft;
t.state = TokenizeState_start;
continue;
}
break;
+ case TokenizeState_angle_bracket_angle_bracket_left_pipe:
+ switch (c) {
+ case 0:
+ t.out->ids.last() = TokenIdBitShiftLeftPipe;
+ goto eof;
+ case '=':
+ t.out->ids.last() = TokenIdBitShiftLeftPipeEq;
+ t.state = TokenizeState_start;
+ break;
+ default:
+ t.out->ids.last() = TokenIdBitShiftLeftPipe;
+ t.state = TokenizeState_start;
+ continue;
+ }
+ break;
case TokenizeState_angle_bracket_right:
switch (c) {
case 0:
@@ -1437,6 +1514,8 @@ const char * token_name(TokenId id) {
case TokenIdBitOrEq: return "|=";
case TokenIdBitShiftLeft: return "<<";
case TokenIdBitShiftLeftEq: return "<<=";
+ case TokenIdBitShiftLeftPipe: return "<<|";
+ case TokenIdBitShiftLeftPipeEq: return "<<|=";
case TokenIdBitShiftRight: return ">>";
case TokenIdBitShiftRightEq: return ">>=";
case TokenIdBitXorEq: return "^=";
@@ -1521,12 +1600,16 @@ const char * token_name(TokenId id) {
case TokenIdMinusEq: return "-=";
case TokenIdMinusPercent: return "-%";
case TokenIdMinusPercentEq: return "-%=";
+ case TokenIdMinusPipe: return "-|";
+ case TokenIdMinusPipeEq: return "-|=";
case TokenIdModEq: return "%=";
case TokenIdPercent: return "%";
case TokenIdPlus: return "+";
case TokenIdPlusEq: return "+=";
case TokenIdPlusPercent: return "+%";
case TokenIdPlusPercentEq: return "+%=";
+ case TokenIdPlusPipe: return "+|";
+ case TokenIdPlusPipeEq: return "+|=";
case TokenIdPlusPlus: return "++";
case TokenIdRBrace: return "}";
case TokenIdRBracket: return "]";
@@ -1542,6 +1625,8 @@ const char * token_name(TokenId id) {
case TokenIdTimesEq: return "*=";
case TokenIdTimesPercent: return "*%";
case TokenIdTimesPercentEq: return "*%=";
+ case TokenIdTimesPipe: return "*|";
+ case TokenIdTimesPipeEq: return "*|=";
case TokenIdBuiltin: return "Builtin";
case TokenIdCount:
zig_unreachable();
diff --git a/src/stage1/tokenizer.hpp b/src/stage1/tokenizer.hpp
index 0e196597eb..56605c1764 100644
--- a/src/stage1/tokenizer.hpp
+++ b/src/stage1/tokenizer.hpp
@@ -23,6 +23,8 @@ enum TokenId : uint8_t {
TokenIdBitOrEq,
TokenIdBitShiftLeft,
TokenIdBitShiftLeftEq,
+ TokenIdBitShiftLeftPipe,
+ TokenIdBitShiftLeftPipeEq,
TokenIdBitShiftRight,
TokenIdBitShiftRightEq,
TokenIdBitXorEq,
@@ -108,12 +110,16 @@ enum TokenId : uint8_t {
TokenIdMinusEq,
TokenIdMinusPercent,
TokenIdMinusPercentEq,
+ TokenIdMinusPipe,
+ TokenIdMinusPipeEq,
TokenIdModEq,
TokenIdPercent,
TokenIdPlus,
TokenIdPlusEq,
TokenIdPlusPercent,
TokenIdPlusPercentEq,
+ TokenIdPlusPipe,
+ TokenIdPlusPipeEq,
TokenIdPlusPlus,
TokenIdRBrace,
TokenIdRBracket,
@@ -129,6 +135,8 @@ enum TokenId : uint8_t {
TokenIdTimesEq,
TokenIdTimesPercent,
TokenIdTimesPercentEq,
+ TokenIdTimesPipe,
+ TokenIdTimesPipeEq,
TokenIdCount,
};
diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig
index 553e9ff21a..7a28ed182d 100644
--- a/test/behavior/saturating_arithmetic.zig
+++ b/test/behavior/saturating_arithmetic.zig
@@ -11,13 +11,34 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
const a = test_data[0];
const b = test_data[1];
const expected = test_data[2];
- const actual = switch (op) {
- .add => @addWithSaturation(a, b),
- .sub => @subWithSaturation(a, b),
- .mul => @mulWithSaturation(a, b),
- .shl => @shlWithSaturation(a, b),
- };
- try expectEqual(expected, actual);
+ {
+ const actual = switch (op) {
+ .add => @addWithSaturation(a, b),
+ .sub => @subWithSaturation(a, b),
+ .mul => @mulWithSaturation(a, b),
+ .shl => @shlWithSaturation(a, b),
+ };
+ try expectEqual(expected, actual);
+ }
+ {
+ const actual = switch (op) {
+ .add => a +| b,
+ .sub => a -| b,
+ .mul => a *| b,
+ .shl => a <<| b,
+ };
+ try expectEqual(expected, actual);
+ }
+ {
+ var actual = a;
+ switch (op) {
+ .add => actual +|= b,
+ .sub => actual -|= b,
+ .mul => actual *|= b,
+ .shl => actual <<|= b,
+ }
+ try expectEqual(expected, actual);
+ }
}
test "@addWithSaturation" {
From b9a95f2dd94e6175322d3388c3936eb600ec90ea Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 15:19:03 -0700
Subject: [PATCH 130/160] sat-arithmetic: add c backend support
- modify AstGen binOpExt()/assignBinOpExt() to accept generic extended payload T
- rework Sema zirSatArithmetic() to use existing sema.analyzeArithmetic() by adding an `opt_extended` parameter.
- add airSatOp() to codegen/c.zig
- add saturating functions to src/link/C/zig.h
---
src/AstGen.zig | 62 +++++++++++-----------
src/Sema.zig | 25 ++++++---
src/codegen/c.zig | 120 +++++++++++++++++++++++++++++++++++++++++--
src/codegen/llvm.zig | 6 +--
src/link/C/zig.h | 93 +++++++++++++++++++++++++++++++++
5 files changed, 262 insertions(+), 44 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index b3af3eb86b..25452cb386 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -535,7 +535,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_left_sat => {
- try assignBinOpExt(gz, scope, node, .shl_with_saturation);
+ try assignBinOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_right => {
@@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_sub_sat => {
- try assignBinOpExt(gz, scope, node, .sub_with_saturation);
+ try assignBinOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mod => {
@@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_add_sat => {
- try assignBinOpExt(gz, scope, node, .add_with_saturation);
+ try assignBinOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mul => {
@@ -596,24 +596,24 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_mul_sat => {
- try assignBinOpExt(gz, scope, node, .mul_with_saturation);
+ try assignBinOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
// zig fmt: off
.bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation),
+ .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
.bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
- .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation),
+ .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
- .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation),
+ .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
- .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation),
+ .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
.bit_and => {
@@ -2713,6 +2713,28 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
+// TODO: is there an existing way to do this?
+// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
+fn binOpExt(
+ gz: *GenZir,
+ scope: *Scope,
+ rl: ResultLoc,
+ infix_node: Ast.Node.Index,
+ lhs_node: Ast.Node.Index,
+ rhs_node: Ast.Node.Index,
+ tag: Zir.Inst.Extended,
+ comptime T: type,
+) InnerError!Zir.Inst.Ref {
+ const lhs = try expr(gz, scope, .none, lhs_node);
+ const rhs = try expr(gz, scope, .none, rhs_node);
+ const result = try gz.addExtendedPayload(tag, T{
+ .node = gz.nodeIndexToRelative(infix_node),
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ return rvalue(gz, rl, result, infix_node);
+}
+
// TODO: is there an existing method to accomplish this?
// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general
fn assignBinOpExt(
@@ -2720,8 +2742,8 @@ fn assignBinOpExt(
scope: *Scope,
infix_node: Ast.Node.Index,
op_inst_tag: Zir.Inst.Extended,
+ comptime T: type,
) InnerError!void {
- try emitDbgNode(gz, infix_node);
const astgen = gz.astgen;
const tree = astgen.tree;
const node_datas = tree.nodes.items(.data);
@@ -2730,7 +2752,7 @@ fn assignBinOpExt(
const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
- const result = try gz.addExtendedPayload(op_inst_tag, Zir.Inst.BinNode{
+ const result = try gz.addExtendedPayload(op_inst_tag, T{
.node = gz.nodeIndexToRelative(infix_node),
.lhs = lhs,
.rhs = rhs,
@@ -7903,26 +7925,6 @@ fn shiftOp(
return rvalue(gz, rl, result, node);
}
-// TODO: is there an existing way to do this?
-// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
-fn binOpExt(
- gz: *GenZir,
- scope: *Scope,
- rl: ResultLoc,
- node: Ast.Node.Index,
- lhs_node: Ast.Node.Index,
- rhs_node: Ast.Node.Index,
- tag: Zir.Inst.Extended,
-) InnerError!Zir.Inst.Ref {
- const lhs = try expr(gz, scope, .none, lhs_node);
- const rhs = try expr(gz, scope, .none, rhs_node);
- const result = try gz.addExtendedPayload(tag, Zir.Inst.Bin{
- .lhs = lhs,
- .rhs = rhs,
- });
- return rvalue(gz, rl, result, node);
-}
-
fn cImport(
gz: *GenZir,
scope: *Scope,
diff --git a/src/Sema.zig b/src/Sema.zig
index de94a8c6b8..a41d330285 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -694,10 +694,11 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
.c_define => return sema.zirCDefine( block, extended),
.wasm_memory_size => return sema.zirWasmMemorySize( block, extended),
.wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended),
- .add_with_saturation=> return sema.zirSatArithmetic( block, extended),
- .sub_with_saturation=> return sema.zirSatArithmetic( block, extended),
- .mul_with_saturation=> return sema.zirSatArithmetic( block, extended),
- .shl_with_saturation=> return sema.zirSatArithmetic( block, extended),
+ .add_with_saturation,
+ .sub_with_saturation,
+ .mul_with_saturation,
+ .shl_with_saturation,
+ => return sema.zirSatArithmetic( block, extended),
// zig fmt: on
}
}
@@ -6163,7 +6164,7 @@ fn zirNegate(
const lhs = sema.resolveInst(.zero);
const rhs = sema.resolveInst(inst_data.operand);
- return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
+ return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src, null);
}
fn zirArithmetic(
@@ -6183,7 +6184,7 @@ fn zirArithmetic(
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
+ return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src, null);
}
fn zirOverflowArithmetic(
@@ -6209,10 +6210,17 @@ fn zirSatArithmetic(
defer tracy.end();
const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
- const src: LazySrcLoc = .{ .node_offset = extra.node };
- return sema.mod.fail(&block.base, src, "TODO implement Sema.zirSatArithmetic", .{});
+ sema.src = .{ .node_offset_bin_op = extra.node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = extra.node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = extra.node };
+ const lhs = sema.resolveInst(extra.lhs);
+ const rhs = sema.resolveInst(extra.rhs);
+
+ return sema.analyzeArithmetic(block, .extended, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
}
+// TODO: audit - not sure if its a good idea to reuse this, adding `opt_extended` param
+// FIXME: somehow, rhs of <<| is required to be Log2T. this should accept T
fn analyzeArithmetic(
sema: *Sema,
block: *Scope.Block,
@@ -6223,6 +6231,7 @@ fn analyzeArithmetic(
src: LazySrcLoc,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
+ opt_extended: ?Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index dce0c10b4c..9ded6fe0e8 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -885,17 +885,17 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
// that wrapping is UB.
.add, .ptr_add => try airBinOp( f, inst, " + "),
.addwrap => try airWrapOp(f, inst, " + ", "addw_"),
- .addsat => return o.dg.fail("TODO: C backend: implement codegen for addsat", .{}),
+ .addsat => return f.fail("TODO: C backend: implement codegen for addsat", .{}),
// TODO use a different strategy for sub that communicates to the optimizer
// that wrapping is UB.
.sub, .ptr_sub => try airBinOp( f, inst, " - "),
.subwrap => try airWrapOp(f, inst, " - ", "subw_"),
- .subsat => return o.dg.fail("TODO: C backend: implement codegen for subsat", .{}),
+ .subsat => return f.fail("TODO: C backend: implement codegen for subsat", .{}),
// TODO use a different strategy for mul that communicates to the optimizer
// that wrapping is UB.
.mul => try airBinOp( f, inst, " * "),
.mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
- .mulsat => return o.dg.fail("TODO: C backend: implement codegen for mulsat", .{}),
+ .mulsat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
// TODO use a different strategy for div that communicates to the optimizer
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
@@ -919,6 +919,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.shr => try airBinOp(f, inst, " >> "),
.shl => try airBinOp(f, inst, " << "),
+ .shl_sat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
+
.not => try airNot( f, inst),
@@ -1312,6 +1314,118 @@ fn airWrapOp(
return ret;
}
+fn airSatOp(
+ o: *Object,
+ inst: Air.Inst.Index,
+ str_op: [*:0]const u8,
+ fn_op: [*:0]const u8,
+) !CValue {
+ if (o.liveness.isUnused(inst))
+ return CValue.none;
+
+ const bin_op = o.air.instructions.items(.data)[inst].bin_op;
+ const inst_ty = o.air.typeOfIndex(inst);
+ const int_info = inst_ty.intInfo(o.dg.module.getTarget());
+ const bits = int_info.bits;
+
+ // if it's an unsigned int with non-arbitrary bit size then we can just add
+ const ok_bits = switch (bits) {
+ 8, 16, 32, 64, 128 => true,
+ else => false,
+ };
+
+ if (bits > 64) {
+ return f.fail("TODO: C backend: airSatOp for large integers", .{});
+ }
+
+ var min_buf: [80]u8 = undefined;
+ const min = switch (int_info.signedness) {
+ .unsigned => "0",
+ else => switch (inst_ty.tag()) {
+ .c_short => "SHRT_MIN",
+ .c_int => "INT_MIN",
+ .c_long => "LONG_MIN",
+ .c_longlong => "LLONG_MIN",
+ .isize => "INTPTR_MIN",
+ else => blk: {
+ const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1));
+ break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) {
+ error.NoSpaceLeft => unreachable,
+ else => |e| return e,
+ };
+ },
+ },
+ };
+
+ var max_buf: [80]u8 = undefined;
+ const max = switch (inst_ty.tag()) {
+ .c_short => "SHRT_MAX",
+ .c_ushort => "USHRT_MAX",
+ .c_int => "INT_MAX",
+ .c_uint => "UINT_MAX",
+ .c_long => "LONG_MAX",
+ .c_ulong => "ULONG_MAX",
+ .c_longlong => "LLONG_MAX",
+ .c_ulonglong => "ULLONG_MAX",
+ .isize => "INTPTR_MAX",
+ .usize => "UINTPTR_MAX",
+ else => blk: {
+ const pow_bits = switch (int_info.signedness) {
+ .signed => bits - 1,
+ .unsigned => bits,
+ };
+ const val = std.math.pow(u65, 2, pow_bits) - 1;
+ break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) {
+ error.NoSpaceLeft => unreachable,
+ else => |e| return e,
+ };
+ },
+ };
+
+ const lhs = try o.resolveInst(bin_op.lhs);
+ const rhs = try o.resolveInst(bin_op.rhs);
+ const w = o.writer();
+
+ const ret = try o.allocLocal(inst_ty, .Mut);
+ try w.print(" = zig_{s}", .{fn_op});
+
+ switch (inst_ty.tag()) {
+ .isize => try w.writeAll("isize"),
+ .c_short => try w.writeAll("short"),
+ .c_int => try w.writeAll("int"),
+ .c_long => try w.writeAll("long"),
+ .c_longlong => try w.writeAll("longlong"),
+ else => {
+ const prefix_byte: u8 = switch (int_info.signedness) {
+ .signed => 'i',
+ .unsigned => 'u',
+ };
+ for ([_]u8{ 8, 16, 32, 64 }) |nbits| {
+ if (bits <= nbits) {
+ try w.print("{c}{d}", .{ prefix_byte, nbits });
+ break;
+ }
+ } else {
+ unreachable;
+ }
+ },
+ }
+
+ try w.writeByte('(');
+ try o.writeCValue(w, lhs);
+ try w.writeAll(", ");
+ try o.writeCValue(w, rhs);
+
+ if (int_info.signedness == .signed) {
+ try w.print(", {s}", .{min});
+ }
+
+ try w.print(", {s});", .{max});
+ try o.indent_writer.insertNewline();
+
+ return ret;
+}
+
fn airNot(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst))
return CValue.none;
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index c21985d1c5..733984a9d6 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -2038,7 +2038,7 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, "");
+ if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, "");
if (ty == .wrapping)
return self.builder.buildAdd(lhs, rhs, "")
else if (ty == .saturated) {
@@ -2060,7 +2060,7 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, "");
+ if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, "");
if (ty == .wrapping)
return self.builder.buildSub(lhs, rhs, "")
else if (ty == .saturated) {
@@ -2082,7 +2082,7 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const inst_ty = self.air.typeOfIndex(inst);
- if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, "");
+ if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, "");
if (ty == .wrapping)
return self.builder.buildMul(lhs, rhs, "")
else if (ty == .saturated) {
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index b34068d1f2..84b1c3dac6 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -356,3 +356,96 @@ static inline long long zig_subw_longlong(long long lhs, long long rhs, long lon
return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs));
}
+/*
+ * Saturating aritmetic operations: add, sub, mul, shl
+ */
+#define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \
+ return (x > max - y) ? max : x + y; \
+}
+
+#define zig_add_sat_s(ZT, T, T2) static inline T zig_adds_##ZT(T2 x, T2 y, T2 min, T2 max) { \
+ T2 res = x + y; \
+ return (res < min) ? min : (res > max) ? max : res; \
+}
+
+zig_add_sat_u( u8, uint8_t)
+zig_add_sat_s( i8, int8_t, int16_t)
+zig_add_sat_u(u16, uint16_t)
+zig_add_sat_s(i16, int16_t, int32_t)
+zig_add_sat_u(u32, uint32_t)
+zig_add_sat_s(i32, int32_t, int64_t)
+zig_add_sat_u(u64, uint64_t)
+zig_add_sat_s(i64, int64_t, int128_t)
+zig_add_sat_s(isize, intptr_t, int128_t)
+zig_add_sat_s(short, short, int)
+zig_add_sat_s(int, int, long)
+zig_add_sat_s(long, long, long long)
+
+#define zig_sub_sat_u(ZT, T) static inline T zig_subs_##ZT(T x, T y, T max) { \
+ return (x > max + y) ? max : x - y; \
+}
+
+#define zig_sub_sat_s(ZT, T, T2) static inline T zig_subs_##ZT(T2 x, T2 y, T2 min, T2 max) { \
+ T2 res = x - y; \
+ return (res < min) ? min : (res > max) ? max : res; \
+}
+
+zig_sub_sat_u( u8, uint8_t)
+zig_sub_sat_s( i8, int8_t, int16_t)
+zig_sub_sat_u(u16, uint16_t)
+zig_sub_sat_s(i16, int16_t, int32_t)
+zig_sub_sat_u(u32, uint32_t)
+zig_sub_sat_s(i32, int32_t, int64_t)
+zig_sub_sat_u(u64, uint64_t)
+zig_sub_sat_s(i64, int64_t, int128_t)
+zig_sub_sat_s(isize, intptr_t, int128_t)
+zig_sub_sat_s(short, short, int)
+zig_sub_sat_s(int, int, long)
+zig_sub_sat_s(long, long, long long)
+
+
+#define zig_mul_sat_u(ZT, T, T2) static inline T zig_muls_##ZT(T2 x, T2 y, T2 max) { \
+ T2 res = x * y; \
+ return (res > max) ? max : res; \
+}
+
+#define zig_mul_sat_s(ZT, T, T2) static inline T zig_muls_##ZT(T2 x, T2 y, T2 min, T2 max) { \
+ T2 res = x * y; \
+ return (res < min) ? min : (res > max) ? max : res; \
+}
+
+zig_mul_sat_u(u8, uint8_t, uint16_t)
+zig_mul_sat_s(i8, int8_t, int16_t)
+zig_mul_sat_u(u16, uint16_t, uint32_t)
+zig_mul_sat_s(i16, int16_t, int32_t)
+zig_mul_sat_u(u32, uint32_t, uint64_t)
+zig_mul_sat_s(i32, int32_t, int64_t)
+zig_mul_sat_u(u64, uint64_t, uint128_t)
+zig_mul_sat_s(i64, int64_t, int128_t)
+zig_mul_sat_s(isize, intptr_t, int128_t)
+zig_mul_sat_s(short, short, int)
+zig_mul_sat_s(int, int, long)
+zig_mul_sat_s(long, long, long long)
+
+#define zig_shl_sat_u(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T max) { \
+ T leading_zeros = __builtin_clz(x); \
+ return (leading_zeros + y > bits) ? max : x << y; \
+}
+
+#define zig_shl_sat_s(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T min, T max) { \
+ T leading_zeros = __builtin_clz(x & ~max); \
+ return (leading_zeros + y > bits) ? max : x << y; \
+}
+
+zig_shl_sat_u(u8, uint8_t, 8)
+zig_shl_sat_s(i8, int8_t, 7)
+zig_shl_sat_u(u16, uint16_t, 16)
+zig_shl_sat_s(i16, int16_t, 15)
+zig_shl_sat_u(u32, uint32_t, 32)
+zig_shl_sat_s(i32, int32_t, 31)
+zig_shl_sat_u(u64, uint64_t, 64)
+zig_shl_sat_s(i64, int64_t, 63)
+zig_shl_sat_s(isize, intptr_t, 63)
+zig_shl_sat_s(short, short, 15)
+zig_shl_sat_s(int, int, 31)
+zig_shl_sat_s(long, long, 63)
\ No newline at end of file
From dcbc52ec85d00fbd3603c314aaaab98fb3866892 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 15:58:37 -0700
Subject: [PATCH 131/160] sat-arithmetic: correctly tokenize <<|, <<|=
- set state rather than result.tag in tokenizer.zig
- add test to tokenizer.zig for <<, <<|, <<|=
---
lib/std/zig/tokenizer.zig | 8 +++++++-
src/Air.zig | 6 +++---
src/stage1/tokenizer.cpp | 1 -
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig
index 6afe7750d3..a7442b8b25 100644
--- a/lib/std/zig/tokenizer.zig
+++ b/lib/std/zig/tokenizer.zig
@@ -1007,7 +1007,7 @@ pub const Tokenizer = struct {
break;
},
'|' => {
- result.tag = .angle_bracket_angle_bracket_left_pipe;
+ state = .angle_bracket_angle_bracket_left_pipe;
},
else => {
result.tag = .angle_bracket_angle_bracket_left;
@@ -2015,6 +2015,12 @@ test "tokenizer - invalid token with unfinished escape right before eof" {
try testTokenize("'\\u", &.{.invalid});
}
+test "tokenizer - saturating" {
+ try testTokenize("<<", &.{.angle_bracket_angle_bracket_left});
+ try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe});
+ try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal});
+}
+
fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void {
var tokenizer = Tokenizer.init(source);
for (expected_tokens) |expected_token_id| {
diff --git a/src/Air.zig b/src/Air.zig
index b7d3938352..00f223ad21 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -44,7 +44,7 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
addwrap,
- /// Saturating integer addition.
+ /// Saturating integer addition.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
@@ -59,7 +59,7 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
subwrap,
- /// Saturating integer subtraction.
+ /// Saturating integer subtraction.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
@@ -74,7 +74,7 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
mulwrap,
- /// Saturating integer multiplication.
+ /// Saturating integer multiplication.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
diff --git a/src/stage1/tokenizer.cpp b/src/stage1/tokenizer.cpp
index 3560193927..47e324c933 100644
--- a/src/stage1/tokenizer.cpp
+++ b/src/stage1/tokenizer.cpp
@@ -995,7 +995,6 @@ void tokenize(const char *source, Tokenization *out) {
t.state = TokenizeState_start;
break;
case '|':
- // t.out->ids.last() = TokenIdBitShiftLeftPipe;
t.state = TokenizeState_angle_bracket_angle_bracket_left_pipe;
break;
default:
From bdb90a07bbf0fdedca71f5deace7087bc562b437 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 16:30:11 -0700
Subject: [PATCH 132/160] sat-arithmetic: fixups zig fmt / astcheck
---
src/AstGen.zig | 2 +-
src/codegen/c.zig | 11 +++++------
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 25452cb386..d3235ace53 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -2786,7 +2786,7 @@ fn assignShift(
fn assignShiftSat(
gz: *GenZir,
scope: *Scope,
- infix_node: ast.Node.Index,
+ infix_node: Ast.Node.Index,
op_inst_tag: Zir.Inst.Tag,
) InnerError!void {
try emitDbgNode(gz, infix_node);
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 9ded6fe0e8..37e19d9e1a 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1317,7 +1317,6 @@ fn airWrapOp(
fn airSatOp(
o: *Object,
inst: Air.Inst.Index,
- str_op: [*:0]const u8,
fn_op: [*:0]const u8,
) !CValue {
if (o.liveness.isUnused(inst))
@@ -1328,12 +1327,12 @@ fn airSatOp(
const int_info = inst_ty.intInfo(o.dg.module.getTarget());
const bits = int_info.bits;
- // if it's an unsigned int with non-arbitrary bit size then we can just add
- const ok_bits = switch (bits) {
- 8, 16, 32, 64, 128 => true,
- else => false,
- };
+ switch (bits) {
+ 8, 16, 32, 64, 128 => {},
+ else => return o.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
+ }
+ // if it's an unsigned int with non-arbitrary bit size then we can just add
if (bits > 64) {
return f.fail("TODO: C backend: airSatOp for large integers", .{});
}
From 6ba9f7474f6999e9239ce6459549667439945bf2 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 18:47:11 -0700
Subject: [PATCH 133/160] sat-arithmetic: fix docgen
---
doc/docgen.zig | 8 ++++++++
src/link/C/zig.h | 2 +-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/doc/docgen.zig b/doc/docgen.zig
index a3ae53a65e..148a8bedb7 100644
--- a/doc/docgen.zig
+++ b/doc/docgen.zig
@@ -1058,15 +1058,21 @@ fn tokenizeAndPrintRaw(
.plus_equal,
.plus_percent,
.plus_percent_equal,
+ .plus_pipe,
+ .plus_pipe_equal,
.minus,
.minus_equal,
.minus_percent,
.minus_percent_equal,
+ .minus_pipe,
+ .minus_pipe_equal,
.asterisk,
.asterisk_equal,
.asterisk_asterisk,
.asterisk_percent,
.asterisk_percent_equal,
+ .asterisk_pipe,
+ .asterisk_pipe_equal,
.arrow,
.colon,
.slash,
@@ -1079,6 +1085,8 @@ fn tokenizeAndPrintRaw(
.angle_bracket_left_equal,
.angle_bracket_angle_bracket_left,
.angle_bracket_angle_bracket_left_equal,
+ .angle_bracket_angle_bracket_left_pipe,
+ .angle_bracket_angle_bracket_left_pipe_equal,
.angle_bracket_right,
.angle_bracket_right_equal,
.angle_bracket_angle_bracket_right,
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index 84b1c3dac6..cb23492490 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -448,4 +448,4 @@ zig_shl_sat_s(i64, int64_t, 63)
zig_shl_sat_s(isize, intptr_t, 63)
zig_shl_sat_s(short, short, 15)
zig_shl_sat_s(int, int, 31)
-zig_shl_sat_s(long, long, 63)
\ No newline at end of file
+zig_shl_sat_s(long, long, 63)
From 0f246257be5029e7bb73ac9a5ff356171007bc7a Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 8 Sep 2021 20:59:55 -0700
Subject: [PATCH 134/160] sat-arithmetic: update langref
---
doc/langref.html.in | 97 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 91 insertions(+), 6 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index a5dfa5c927..b6f49dab62 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1244,8 +1244,9 @@ fn divide(a: i32, b: i32) i32 {
Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on
- integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and
- {#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets.
+ integer overflow. Alternative operators are provided for wrapping and saturating arithmetic on all targets.
+ {#syntax#}+%{#endsyntax#} and {#syntax#}-%{#endsyntax#} perform wrapping arithmetic
+ while {#syntax#}+|{#endsyntax#} and {#syntax#}-|{#endsyntax#} perform saturating arithmetic.
Zig supports arbitrary bit-width integers, referenced by using
@@ -1395,6 +1396,24 @@ a +%= b{#endsyntax#}
{#syntax#}@as(u32, std.math.maxInt(u32)) +% 1 == 0{#endsyntax#}
+
+ {#syntax#}a +| b
+a +|= b{#endsyntax#} |
+
+
+ |
+ Saturating Addition.
+
+ - Invokes {#link|Peer Type Resolution#} for the operands.
+ - See also {#link|@addWithSaturation#}.
+
+ |
+
+ {#syntax#}@as(u32, std.math.maxInt(u32)) +| 1 == @as(u32, std.math.maxInt(u32)){#endsyntax#}
+ |
+
{#syntax#}a - b
a -= b{#endsyntax#} |
@@ -1434,6 +1453,24 @@ a -%= b{#endsyntax#}
{#syntax#}@as(u32, 0) -% 1 == std.math.maxInt(u32){#endsyntax#}
+
+ {#syntax#}a -| b
+a -|= b{#endsyntax#} |
+
+
+ |
+ Saturating Subtraction.
+
+ - Invokes {#link|Peer Type Resolution#} for the operands.
+ - See also {#link|@subWithSaturation#}.
+
+ |
+
+ {#syntax#}@as(u32, 0) -| 1 == 0{#endsyntax#}
+ |
+
{#syntax#}-a{#endsyntax#} |
@@ -1508,6 +1545,24 @@ a *%= b{#endsyntax#}
{#syntax#}@as(u8, 200) *% 2 == 144{#endsyntax#}
|
+
+ {#syntax#}a *| b
+a *|= b{#endsyntax#} |
+
+
+ |
+ Saturating Multiplication.
+
+ - Invokes {#link|Peer Type Resolution#} for the operands.
+ - See also {#link|@mulWithSaturation#}.
+
+ |
+
+ {#syntax#}@as(u8, 200) *| 2 == 255{#endsyntax#}
+ |
+
{#syntax#}a / b
a /= b{#endsyntax#} |
@@ -1577,6 +1632,24 @@ a <<= b{#endsyntax#}
{#syntax#}1 << 8 == 256{#endsyntax#}
+
+ {#syntax#}a <<| b
+a <<|= b{#endsyntax#} |
+
+
+ |
+ Saturating Bit Shift Left.
+
+ - See also {#link|@shlExact#}.
+ - See also {#link|@shlWithOverflow#}.
+
+ |
+
+ {#syntax#}@as(u8, 1) <<| 8 == 255{#endsyntax#}
+ |
+
{#syntax#}a >> b
a >>= b{#endsyntax#} |
@@ -1968,14 +2041,14 @@ const B = error{Two};
a!b
x{}
!x -x -%x ~x &x ?x
-* / % ** *% ||
-+ - ++ +% -%
-<< >>
+* / % ** *% *| ||
++ - ++ +% -% +| -|
+<< >> <<|
& ^ | orelse catch
== != < > <= >=
and
or
-= *= /= %= += -= <<= >>= &= ^= |={#endsyntax#}
+= *= *%= *|= /= %= += +%= +|= -= -%= -|= <<= <<|= >>= &= ^= |={#endsyntax#}
{#header_close#}
{#header_close#}
{#header_open|Arrays#}
@@ -11839,6 +11912,7 @@ AssignOp
/ PLUSEQUAL
/ MINUSEQUAL
/ LARROW2EQUAL
+ / LARROW2PIPEEQUAL
/ RARROW2EQUAL
/ AMPERSANDEQUAL
/ CARETEQUAL
@@ -11873,6 +11947,8 @@ AdditionOp
/ PLUS2
/ PLUSPERCENT
/ MINUSPERCENT
+ / PLUSPIPE
+ / MINUSPIPE
MultiplyOp
<- PIPE2
@@ -11881,6 +11957,7 @@ MultiplyOp
/ PERCENT
/ ASTERISK2
/ ASTERISKPERCENT
+ / ASTERISKPIPE
PrefixOp
<- EXCLAMATIONMARK
@@ -12044,6 +12121,8 @@ ASTERISK2 <- '**' skip
ASTERISKEQUAL <- '*=' skip
ASTERISKPERCENT <- '*%' ![=] skip
ASTERISKPERCENTEQUAL <- '*%=' skip
+ASTERISKPIPE <- '*|' ![=] skip
+ASTERISKPIPEEQUAL <- '*|=' skip
CARET <- '^' ![=] skip
CARETEQUAL <- '^=' skip
COLON <- ':' skip
@@ -12060,6 +12139,8 @@ EXCLAMATIONMARK <- '!' ![=] skip
EXCLAMATIONMARKEQUAL <- '!=' skip
LARROW <- '<' ![<=] skip
LARROW2 <- '<<' ![=] skip
+LARROW2PIPE <- '<<|' ![=] skip
+LARROW2PIPEEQUAL <- '<<|=' ![=] skip
LARROW2EQUAL <- '<<=' skip
LARROWEQUAL <- '<=' skip
LBRACE <- '{' skip
@@ -12069,6 +12150,8 @@ MINUS <- '-' ![%=>] skip
MINUSEQUAL <- '-=' skip
MINUSPERCENT <- '-%' ![=] skip
MINUSPERCENTEQUAL <- '-%=' skip
+MINUSPIPE <- '-|' ![=] skip
+MINUSPIPEEQUAL <- '-|=' skip
MINUSRARROW <- '->' skip
PERCENT <- '%' ![=] skip
PERCENTEQUAL <- '%=' skip
@@ -12080,6 +12163,8 @@ PLUS2 <- '++' skip
PLUSEQUAL <- '+=' skip
PLUSPERCENT <- '+%' ![=] skip
PLUSPERCENTEQUAL <- '+%=' skip
+PLUSPIPE <- '+|' ![=] skip
+PLUSPIPEEQUAL <- '+|=' skip
LETTERC <- 'c' skip
QUESTIONMARK <- '?' skip
RARROW <- '>' ![>=] skip
From 1d86eae5269edcac5f32d166f13ed27483f07688 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Thu, 9 Sep 2021 13:07:59 -0700
Subject: [PATCH 135/160] sat-arithmetic: langref - remove syntax disclaimer
---
doc/langref.html.in | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index b6f49dab62..5b3c26b937 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -7241,8 +7241,7 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a +| b{#endsyntax#} will be equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a +| b{#endsyntax#} is equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
{#header_close#}
{#header_open|@alignCast#}
@@ -8372,8 +8371,7 @@ test "@wasmMemoryGrow" {
Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a *| b{#endsyntax#} will be equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a *| b{#endsyntax#} is equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers.
@@ -8629,8 +8627,7 @@ test "@setRuntimeSafety" {
Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a <<| b{#endsyntax#} will be equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a <<| b{#endsyntax#} is equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined.
@@ -8954,8 +8951,7 @@ fn doTheTest() !void {
Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
- Once Saturating arithmetic.
- is completed, the syntax {#syntax#}a -| b{#endsyntax#} will be equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
+ The syntax {#syntax#}a -| b{#endsyntax#} is equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
{#header_close#}
From 487059535242e2b94303502806feaa99d560c63b Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Thu, 9 Sep 2021 14:17:59 -0700
Subject: [PATCH 136/160] sat-arithmetic: add additional tokenizer tests
---
lib/std/zig/tokenizer.zig | 12 ++++++++++++
src/codegen/c.zig | 1 +
2 files changed, 13 insertions(+)
diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig
index a7442b8b25..02fa3dd381 100644
--- a/lib/std/zig/tokenizer.zig
+++ b/lib/std/zig/tokenizer.zig
@@ -2019,6 +2019,18 @@ test "tokenizer - saturating" {
try testTokenize("<<", &.{.angle_bracket_angle_bracket_left});
try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe});
try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal});
+
+ try testTokenize("*", &.{.asterisk});
+ try testTokenize("*|", &.{.asterisk_pipe});
+ try testTokenize("*|=", &.{.asterisk_pipe_equal});
+
+ try testTokenize("+", &.{.plus});
+ try testTokenize("+|", &.{.plus_pipe});
+ try testTokenize("+|=", &.{.plus_pipe_equal});
+
+ try testTokenize("-", &.{.minus});
+ try testTokenize("-|", &.{.minus_pipe});
+ try testTokenize("-|=", &.{.minus_pipe_equal});
}
fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 37e19d9e1a..6101740eea 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1347,6 +1347,7 @@ fn airSatOp(
.c_longlong => "LLONG_MIN",
.isize => "INTPTR_MIN",
else => blk: {
+ // compute the type minimum based on the bitcount (bits)
const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1));
break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) {
error.NoSpaceLeft => unreachable,
From fd8383545adc5f202e8098bd13b3bda3481ad235 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Fri, 10 Sep 2021 15:38:49 -0700
Subject: [PATCH 137/160] sat-arithmetic: langref - use tags
---
doc/langref.html.in | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 5b3c26b937..e750797997 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1397,8 +1397,8 @@ a +%= b{#endsyntax#} |
- {#syntax#}a +| b
-a +|= b{#endsyntax#} |
+ {#syntax#}a +| b
+a +|= b{#endsyntax#} |
- {#link|Integers#}
@@ -1454,8 +1454,8 @@ a -%= b{#endsyntax#}
|
- {#syntax#}a -| b
-a -|= b{#endsyntax#} |
+ {#syntax#}a -| b
+a -|= b{#endsyntax#} |
- {#link|Integers#}
@@ -1546,8 +1546,8 @@ a *%= b{#endsyntax#}
|
- {#syntax#}a *| b
-a *|= b{#endsyntax#} |
+ {#syntax#}a *| b
+a *|= b{#endsyntax#} |
- {#link|Integers#}
@@ -1633,8 +1633,8 @@ a <<= b{#endsyntax#}
|
- {#syntax#}a <<| b
-a <<|= b{#endsyntax#} |
+ {#syntax#}a <<| b
+a <<|= b{#endsyntax#} |
- {#link|Integers#}
From 68050852fac6940d04e15900f135e6fc88845f9b Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Fri, 10 Sep 2021 15:41:43 -0700
Subject: [PATCH 138/160] sat-arithmetic: minor formatting changes
---
lib/std/zig/Ast.zig | 2 +-
src/codegen/llvm.zig | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index 3632551d17..b69da459d3 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -2577,7 +2577,7 @@ pub const Node = struct {
array_mult,
/// `lhs *% rhs`. main_token is the `*%`.
mul_wrap,
- /// `lhs *| rhs`. main_token is the `*%`.
+ /// `lhs *| rhs`. main_token is the `*|`.
mul_sat,
/// `lhs + rhs`. main_token is the `+`.
add,
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 733984a9d6..cdd19146b5 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1256,7 +1256,7 @@ pub const FuncGen = struct {
.xor => try self.airXor(inst),
.shl => try self.airShl(inst, false),
- .shl_sat => try self.airShl(inst, true),
+ .shl_sat => try self.airShl(inst, true),
.shr => try self.airShr(inst),
.cmp_eq => try self.airCmp(inst, .eq),
From cd8d8add9153b17b4579c2e8951ac3f3f42e1bcd Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Tue, 14 Sep 2021 18:26:28 -0700
Subject: [PATCH 139/160] sat-arithmetic: fix shl methods in cbe
---
src/link/C/zig.h | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index cb23492490..5c9d750729 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -428,17 +428,21 @@ zig_mul_sat_s(int, int, long)
zig_mul_sat_s(long, long, long long)
#define zig_shl_sat_u(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T max) { \
- T leading_zeros = __builtin_clz(x); \
- return (leading_zeros + y > bits) ? max : x << y; \
+ if(x == 0) return 0; \
+ T bits_set = 64 - __builtin_clzll(x); \
+ return (bits_set + y > bits) ? max : x << y; \
}
#define zig_shl_sat_s(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T min, T max) { \
- T leading_zeros = __builtin_clz(x & ~max); \
- return (leading_zeros + y > bits) ? max : x << y; \
+ if(x == 0) return 0; \
+ T x_twos_comp = x < 0 ? -x : x; \
+ T bits_set = 64 - __builtin_clzll(x_twos_comp); \
+ T min_or_max = (x < 0) ? min : max; \
+ return (y + bits_set > bits ) ? min_or_max : x << y; \
}
-zig_shl_sat_u(u8, uint8_t, 8)
-zig_shl_sat_s(i8, int8_t, 7)
+zig_shl_sat_u(u8, uint8_t, 8)
+zig_shl_sat_s(i8, int8_t, 7)
zig_shl_sat_u(u16, uint16_t, 16)
zig_shl_sat_s(i16, int16_t, 15)
zig_shl_sat_u(u32, uint32_t, 32)
From baaec94fe427efad4fe46ee3ffde53184cbd0ae9 Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Tue, 14 Sep 2021 18:34:52 -0700
Subject: [PATCH 140/160] sat-arithmetic: create Sema.analyzeSatArithmetic
- similar to Sema.analyzeArithmetic but uses accepts Zir.Inst.Extended.InstData
- missing support for Pointer types and comptime arithmetic
---
src/AstGen.zig | 26 +++++++--------
src/Sema.zig | 90 ++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 96 insertions(+), 20 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index d3235ace53..9dc09ecd27 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -535,7 +535,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_left_sat => {
- try assignBinOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_bit_shift_right => {
@@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_sub_sat => {
- try assignBinOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mod => {
@@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_add_sat => {
- try assignBinOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
.assign_mul => {
@@ -596,26 +596,28 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_mul_sat => {
- try assignBinOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
return rvalue(gz, rl, .void_value, node);
},
// zig fmt: off
.bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
.bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
- .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
- .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
- .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
+
+ .add_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
+ .sub_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
+ .mul_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
+ .bit_shift_left_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
+
.bit_and => {
const current_ampersand_token = main_tokens[node];
if (token_tags[current_ampersand_token + 1] == .ampersand) {
@@ -2713,9 +2715,7 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
-// TODO: is there an existing way to do this?
-// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general
-fn binOpExt(
+fn simpleBinOpExt(
gz: *GenZir,
scope: *Scope,
rl: ResultLoc,
@@ -2735,9 +2735,7 @@ fn binOpExt(
return rvalue(gz, rl, result, infix_node);
}
-// TODO: is there an existing method to accomplish this?
-// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general
-fn assignBinOpExt(
+fn assignOpExt(
gz: *GenZir,
scope: *Scope,
infix_node: Ast.Node.Index,
diff --git a/src/Sema.zig b/src/Sema.zig
index a41d330285..be10b6d663 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -6164,7 +6164,7 @@ fn zirNegate(
const lhs = sema.resolveInst(.zero);
const rhs = sema.resolveInst(inst_data.operand);
- return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src, null);
+ return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src);
}
fn zirArithmetic(
@@ -6184,7 +6184,7 @@ fn zirArithmetic(
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src, null);
+ return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
}
fn zirOverflowArithmetic(
@@ -6216,11 +6216,90 @@ fn zirSatArithmetic(
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
- return sema.analyzeArithmetic(block, .extended, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
+ return sema.analyzeSatArithmetic(block, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
+}
+
+fn analyzeSatArithmetic(
+ sema: *Sema,
+ block: *Scope.Block,
+ lhs: Air.Inst.Ref,
+ rhs: Air.Inst.Ref,
+ src: LazySrcLoc,
+ lhs_src: LazySrcLoc,
+ rhs_src: LazySrcLoc,
+ extended: Zir.Inst.Extended.InstData,
+) CompileError!Air.Inst.Ref {
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+ if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
+ if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) {
+ return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
+ lhs_ty.arrayLen(), rhs_ty.arrayLen(),
+ });
+ }
+ return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
+ } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
+ return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
+ lhs_ty, rhs_ty,
+ });
+ }
+
+ if (lhs_zig_ty_tag == .Pointer or rhs_zig_ty_tag == .Pointer)
+ return sema.mod.fail(&block.base, src, "TODO implement support for pointers in zirSatArithmetic", .{});
+
+ const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } });
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
+ resolved_type.elemType()
+ else
+ resolved_type;
+
+ const scalar_tag = scalar_type.zigTypeTag();
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ if (!is_int)
+ return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
+ @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
+ });
+
+ if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
+ if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
+ if (lhs_val.isUndef() or rhs_val.isUndef()) {
+ return sema.addConstUndef(resolved_type);
+ }
+ // incase rhs is 0, simply return lhs without doing any calculations
+ if (rhs_val.compareWithZero(.eq)) {
+ switch (extended.opcode) {
+ .add_with_saturation, .sub_with_saturation => return sema.addConstant(scalar_type, lhs_val),
+ else => {},
+ }
+ }
+
+ return sema.mod.fail(&block.base, src, "TODO implement comptime saturating arithmetic for operand '{s}'", .{@tagName(extended.opcode)});
+ } else {
+ try sema.requireRuntimeBlock(block, rhs_src);
+ }
+ } else {
+ try sema.requireRuntimeBlock(block, lhs_src);
+ }
+
+ const air_tag: Air.Inst.Tag = switch (extended.opcode) {
+ .add_with_saturation => .addsat,
+ .sub_with_saturation => .subsat,
+ .mul_with_saturation => .mulsat,
+ .shl_with_saturation => .shl_sat,
+ else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for extended opcode '{s}'", .{@tagName(extended.opcode)}),
+ };
+
+ return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
-// TODO: audit - not sure if its a good idea to reuse this, adding `opt_extended` param
-// FIXME: somehow, rhs of <<| is required to be Log2T. this should accept T
fn analyzeArithmetic(
sema: *Sema,
block: *Scope.Block,
@@ -6231,7 +6310,6 @@ fn analyzeArithmetic(
src: LazySrcLoc,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
- opt_extended: ?Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
From 38703dc9c2dccc43c77ec8dcfe0df936cced9d7a Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Tue, 14 Sep 2021 18:40:28 -0700
Subject: [PATCH 141/160] sat-arithmetic: don't test builtins in behavior tests
- not necessary as we are testing the operators
---
test/behavior/saturating_arithmetic.zig | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig
index 7a28ed182d..5d7a229c3c 100644
--- a/test/behavior/saturating_arithmetic.zig
+++ b/test/behavior/saturating_arithmetic.zig
@@ -11,15 +11,6 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
const a = test_data[0];
const b = test_data[1];
const expected = test_data[2];
- {
- const actual = switch (op) {
- .add => @addWithSaturation(a, b),
- .sub => @subWithSaturation(a, b),
- .mul => @mulWithSaturation(a, b),
- .shl => @shlWithSaturation(a, b),
- };
- try expectEqual(expected, actual);
- }
{
const actual = switch (op) {
.add => a +| b,
From 51673bcb315d837ee0fe80dc50571c2c07d80a2c Mon Sep 17 00:00:00 2001
From: Travis Staloch
Date: Wed, 22 Sep 2021 00:14:08 -0700
Subject: [PATCH 142/160] get build passing again after rebase
---
src/codegen/c.zig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 6101740eea..1afa81b70f 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1334,7 +1334,7 @@ fn airSatOp(
// if it's an unsigned int with non-arbitrary bit size then we can just add
if (bits > 64) {
- return f.fail("TODO: C backend: airSatOp for large integers", .{});
+ return o.dg.fail("TODO: C backend: airSatOp for large integers", .{});
}
var min_buf: [80]u8 = undefined;
From 6cb7906394281370173cd1c1fc79ad93562005a0 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 16:55:42 -0700
Subject: [PATCH 143/160] add missing zig fmt test for saturating arithmetic
---
lib/std/zig/parser_test.zig | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index 2f79cc175c..57f081decb 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -4739,6 +4739,26 @@ test "zig fmt: assignment with inline for and inline while" {
);
}
+test "zig fmt: saturating arithmetic" {
+ try testCanonical(
+ \\test {
+ \\ const actual = switch (op) {
+ \\ .add => a +| b,
+ \\ .sub => a -| b,
+ \\ .mul => a *| b,
+ \\ .shl => a <<| b,
+ \\ };
+ \\ switch (op) {
+ \\ .add => actual +|= b,
+ \\ .sub => actual -|= b,
+ \\ .mul => actual *|= b,
+ \\ .shl => actual <<|= b,
+ \\ }
+ \\}
+ \\
+ );
+}
+
test "zig fmt: insert trailing comma if there are comments between switch values" {
try testTransform(
\\const a = switch (b) {
From 71da169c67ad544bd1d4dfc4bfff9fe302e8284d Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 16:55:57 -0700
Subject: [PATCH 144/160] AstGen: delete dead code
---
src/AstGen.zig | 23 -----------------------
1 file changed, 23 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 9dc09ecd27..92087a7719 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -2781,29 +2781,6 @@ fn assignShift(
_ = try gz.addBin(.store, lhs_ptr, result);
}
-fn assignShiftSat(
- gz: *GenZir,
- scope: *Scope,
- infix_node: Ast.Node.Index,
- op_inst_tag: Zir.Inst.Tag,
-) InnerError!void {
- try emitDbgNode(gz, infix_node);
- const astgen = gz.astgen;
- const tree = astgen.tree;
- const node_datas = tree.nodes.items(.data);
-
- const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
- const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
- const rhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
- const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs);
-
- const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{
- .lhs = lhs,
- .rhs = rhs,
- });
- _ = try gz.addBin(.store, lhs_ptr, result);
-}
-
fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const tree = astgen.tree;
From 54675824449d16029fdf6a1873e78cb8f2147f60 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 18:55:43 -0700
Subject: [PATCH 145/160] saturating arithmetic modifications
* Remove the builtins `@addWithSaturation`, `@subWithSaturation`,
`@mulWithSaturation`, and `@shlWithSaturation` now that we have
first-class syntax for saturating arithmetic.
* langref: Clarify the behavior of `@shlExact`.
* Ast: rename `bit_shift_left` to `shl` and `bit_shift_right` to `shr`
for consistency.
* Air: rename to include underscore separator with consistency with
the rest of the ops.
* Air: add shl_exact instruction
* Use non-extended tags for saturating arithmetic, to keep it
simple so that all the arithmetic operations can be done the same
way.
- Sema: unify analyzeArithmetic with analyzeSatArithmetic
- implement comptime `+|`, `-|`, and `*|`
- allow float operands to saturating arithmetic
* `<<|` allows any integer type for the RHS.
* C backend: fix rebase conflicts
* LLVM backend: reduce the amount of branching for arithmetic ops
* zig.h: fix magic number not matching actual size of C integer types
---
doc/langref.html.in | 63 +------
lib/std/zig/Ast.zig | 36 ++--
lib/std/zig/parse.zig | 12 +-
lib/std/zig/render.zig | 20 +-
src/Air.zig | 18 +-
src/AstGen.zig | 167 ++++++-----------
src/BuiltinFn.zig | 32 ----
src/Liveness.zig | 7 +-
src/Sema.zig | 234 ++++++++++++------------
src/Zir.zig | 143 +++++++--------
src/codegen.zig | 56 ++++--
src/codegen/c.zig | 76 ++++----
src/codegen/llvm.zig | 188 +++++++++++++------
src/codegen/llvm/bindings.zig | 6 +
src/link/C/zig.h | 11 +-
src/print_air.zig | 7 +-
src/print_zir.zig | 22 +--
src/stage1/all_types.hpp | 4 -
src/stage1/astgen.cpp | 60 ------
src/stage1/codegen.cpp | 4 -
src/translate_c/ast.zig | 8 +-
src/value.zig | 87 +++++++++
test/behavior/saturating_arithmetic.zig | 33 +---
23 files changed, 623 insertions(+), 671 deletions(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index e750797997..2e69e37097 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1407,7 +1407,6 @@ a +|= b{#endsyntax#}
Saturating Addition.
- Invokes {#link|Peer Type Resolution#} for the operands.
- - See also {#link|@addWithSaturation#}.
|
@@ -1464,7 +1463,6 @@ a -|= b{#endsyntax#}
| Saturating Subtraction.
- Invokes {#link|Peer Type Resolution#} for the operands.
- - See also {#link|@subWithSaturation#}.
|
@@ -1556,7 +1554,6 @@ a *|= b{#endsyntax#}
| Saturating Multiplication.
- Invokes {#link|Peer Type Resolution#} for the operands.
- - See also {#link|@mulWithSaturation#}.
|
@@ -7235,15 +7232,6 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
{#header_close#}
- {#header_open|@addWithSaturation#}
- {#syntax#}@addWithSaturation(a: T, b: T) T{#endsyntax#}
-
- Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
-
-
- The syntax {#syntax#}a +| b{#endsyntax#} is equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.
-
- {#header_close#}
{#header_open|@alignCast#}
{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}
@@ -8365,21 +8353,6 @@ test "@wasmMemoryGrow" {
{#header_close#}
- {#header_open|@mulWithSaturation#}
- {#syntax#}@mulWithSaturation(a: T, b: T) T{#endsyntax#}
-
- Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
-
-
- The syntax {#syntax#}a *| b{#endsyntax#} is equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.
-
-
- NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers.
- This may result in an incorrect sign bit when there is overflow. This will be fixed in zig's 0.9.0 release.
- Check this issue for more information.
-
- {#header_close#}
-
{#header_open|@panic#}
{#syntax#}@panic(message: []const u8) noreturn{#endsyntax#}
@@ -8597,14 +8570,16 @@ test "@setRuntimeSafety" {
{#header_open|@shlExact#}
{#syntax#}@shlExact(value: T, shift_amt: Log2T) T{#endsyntax#}
- Performs the left shift operation ({#syntax#}<<{#endsyntax#}). Caller guarantees
- that the shift will not shift any 1 bits out.
+ Performs the left shift operation ({#syntax#}<<{#endsyntax#}).
+ For unsigned integers, the result is {#link|undefined#} if any 1 bits
+ are shifted out. For signed integers, the result is {#link|undefined#} if
+ any bits that disagree with the resultant sign bit are shifted out.
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
- {#see_also|@shrExact|@shlWithOverflow|@shlWithSaturation#}
+ {#see_also|@shrExact|@shlWithOverflow#}
{#header_close#}
{#header_open|@shlWithOverflow#}
@@ -8618,23 +8593,9 @@ test "@setRuntimeSafety" {
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
- {#see_also|@shlExact|@shrExact|@shlWithSaturation#}
+ {#see_also|@shlExact|@shrExact#}
{#header_close#}
- {#header_open|@shlWithSaturation#}
- {#syntax#}@shlWithSaturation(a: T, shift_amt: T) T{#endsyntax#}
-
- Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.
-
-
- The syntax {#syntax#}a <<| b{#endsyntax#} is equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.
-
-
- Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined.
-
- {#see_also|@shlExact|@shrExact|@shlWithOverflow#}
- {#header_close#}
-
{#header_open|@shrExact#}
{#syntax#}@shrExact(value: T, shift_amt: Log2T) T{#endsyntax#}
@@ -8645,7 +8606,7 @@ test "@setRuntimeSafety" {
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.
- {#see_also|@shlExact|@shlWithOverflow|@shlWithSaturation#}
+ {#see_also|@shlExact|@shlWithOverflow#}
{#header_close#}
{#header_open|@shuffle#}
@@ -8945,16 +8906,6 @@ fn doTheTest() !void {
{#header_close#}
- {#header_open|@subWithSaturation#}
- {#syntax#}@subWithSaturation(a: T, b: T) T{#endsyntax#}
-
- Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.
-
-
- The syntax {#syntax#}a -| b{#endsyntax#} is equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.
-
- {#header_close#}
-
{#header_open|@tagName#}
{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index b69da459d3..4ee3a45221 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -395,9 +395,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mod,
.assign_add,
.assign_sub,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_and,
.assign_bit_xor,
.assign_bit_or,
@@ -422,9 +422,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex {
.sub_wrap,
.add_sat,
.sub_sat,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_and,
.bit_xor,
.bit_or,
@@ -659,9 +659,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.assign_mod,
.assign_add,
.assign_sub,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_and,
.assign_bit_xor,
.assign_bit_or,
@@ -686,9 +686,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex {
.sub_wrap,
.add_sat,
.sub_sat,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_and,
.bit_xor,
.bit_or,
@@ -2540,11 +2540,11 @@ pub const Node = struct {
/// `lhs -= rhs`. main_token is op.
assign_sub,
/// `lhs <<= rhs`. main_token is op.
- assign_bit_shift_left,
+ assign_shl,
/// `lhs <<|= rhs`. main_token is op.
- assign_bit_shift_left_sat,
+ assign_shl_sat,
/// `lhs >>= rhs`. main_token is op.
- assign_bit_shift_right,
+ assign_shr,
/// `lhs &= rhs`. main_token is op.
assign_bit_and,
/// `lhs ^= rhs`. main_token is op.
@@ -2594,11 +2594,11 @@ pub const Node = struct {
/// `lhs -| rhs`. main_token is the `-|`.
sub_sat,
/// `lhs << rhs`. main_token is the `<<`.
- bit_shift_left,
+ shl,
/// `lhs <<| rhs`. main_token is the `<<|`.
- bit_shift_left_sat,
+ shl_sat,
/// `lhs >> rhs`. main_token is the `>>`.
- bit_shift_right,
+ shr,
/// `lhs & rhs`. main_token is the `&`.
bit_and,
/// `lhs ^ rhs`. main_token is the `^`.
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
index a2780b5225..021b028455 100644
--- a/lib/std/zig/parse.zig
+++ b/lib/std/zig/parse.zig
@@ -1268,9 +1268,9 @@ const Parser = struct {
.percent_equal => .assign_mod,
.plus_equal => .assign_add,
.minus_equal => .assign_sub,
- .angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left,
- .angle_bracket_angle_bracket_left_pipe_equal => .assign_bit_shift_left_sat,
- .angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right,
+ .angle_bracket_angle_bracket_left_equal => .assign_shl,
+ .angle_bracket_angle_bracket_left_pipe_equal => .assign_shl_sat,
+ .angle_bracket_angle_bracket_right_equal => .assign_shr,
.ampersand_equal => .assign_bit_and,
.caret_equal => .assign_bit_xor,
.pipe_equal => .assign_bit_or,
@@ -1346,9 +1346,9 @@ const Parser = struct {
.keyword_orelse = .{ .prec = 40, .tag = .@"orelse" },
.keyword_catch = .{ .prec = 40, .tag = .@"catch" },
- .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .bit_shift_left },
- .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .bit_shift_left_sat },
- .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .bit_shift_right },
+ .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .shl },
+ .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .shl_sat },
+ .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .shr },
.plus = .{ .prec = 60, .tag = .add },
.minus = .{ .prec = 60, .tag = .sub },
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
index 47f019d1cf..4357960251 100644
--- a/lib/std/zig/render.zig
+++ b/lib/std/zig/render.zig
@@ -339,9 +339,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -357,9 +357,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index,
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
@@ -2528,8 +2528,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -2542,8 +2542,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_right,
+ .shl,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
diff --git a/src/Air.zig b/src/Air.zig
index 00f223ad21..f05c18e87a 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -48,7 +48,7 @@ pub const Inst = struct {
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
- addsat,
+ add_sat,
/// Float or integer subtraction. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -63,7 +63,7 @@ pub const Inst = struct {
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
- subsat,
+ sub_sat,
/// Float or integer multiplication. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -78,7 +78,7 @@ pub const Inst = struct {
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
- mulsat,
+ mul_sat,
/// Integer or float division. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -125,6 +125,11 @@ pub const Inst = struct {
/// Shift left. `<<`
/// Uses the `bin_op` field.
shl,
+ /// Shift left; For unsigned integers, the shift produces a poison value if it shifts
+ /// out any non-zero bits. For signed integers, the shift produces a poison value if
+ /// it shifts out any bits that disagree with the resultant sign bit.
+ /// Uses the `bin_op` field.
+ shl_exact,
/// Shift left saturating. `<<|`
/// Uses the `bin_op` field.
shl_sat,
@@ -586,13 +591,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.add,
.addwrap,
- .addsat,
+ .add_sat,
.sub,
.subwrap,
- .subsat,
+ .sub_sat,
.mul,
.mulwrap,
- .mulsat,
+ .mul_sat,
.div,
.rem,
.mod,
@@ -603,6 +608,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.ptr_sub,
.shr,
.shl,
+ .shl_exact,
.shl_sat,
=> return air.typeOf(datas[inst].bin_op.lhs),
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 92087a7719..847860630a 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -317,9 +317,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -345,9 +345,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
.mod,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bang_equal,
.equal_equal,
@@ -530,15 +530,15 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
- .assign_bit_shift_left => {
+ .assign_shl => {
try assignShift(gz, scope, node, .shl);
return rvalue(gz, rl, .void_value, node);
},
- .assign_bit_shift_left_sat => {
- try assignOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic);
+ .assign_shl_sat => {
+ try assignShiftSat(gz, scope, node);
return rvalue(gz, rl, .void_value, node);
},
- .assign_bit_shift_right => {
+ .assign_shr => {
try assignShift(gz, scope, node, .shr);
return rvalue(gz, rl, .void_value, node);
},
@@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_sub_sat => {
- try assignOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOp(gz, scope, node, .sub_sat);
return rvalue(gz, rl, .void_value, node);
},
.assign_mod => {
@@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_add_sat => {
- try assignOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOp(gz, scope, node, .add_sat);
return rvalue(gz, rl, .void_value, node);
},
.assign_mul => {
@@ -596,28 +596,27 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
return rvalue(gz, rl, .void_value, node);
},
.assign_mul_sat => {
- try assignOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic);
+ try assignOp(gz, scope, node, .mul_sat);
return rvalue(gz, rl, .void_value, node);
},
// zig fmt: off
- .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
- .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
+ .shl => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl),
+ .shr => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr),
.add => return simpleBinOp(gz, scope, rl, node, .add),
.add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap),
+ .add_sat => return simpleBinOp(gz, scope, rl, node, .add_sat),
.sub => return simpleBinOp(gz, scope, rl, node, .sub),
.sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap),
+ .sub_sat => return simpleBinOp(gz, scope, rl, node, .sub_sat),
.mul => return simpleBinOp(gz, scope, rl, node, .mul),
.mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap),
+ .mul_sat => return simpleBinOp(gz, scope, rl, node, .mul_sat),
.div => return simpleBinOp(gz, scope, rl, node, .div),
.mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
+ .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat),
- .add_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic),
- .sub_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic),
- .mul_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic),
- .bit_shift_left_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic),
-
.bit_and => {
const current_ampersand_token = main_tokens[node];
if (token_tags[current_ampersand_token + 1] == .ampersand) {
@@ -1928,8 +1927,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod
.assign => try assign(gz, scope, statement),
- .assign_bit_shift_left => try assignShift(gz, scope, statement, .shl),
- .assign_bit_shift_right => try assignShift(gz, scope, statement, .shr),
+ .assign_shl => try assignShift(gz, scope, statement, .shl),
+ .assign_shr => try assignShift(gz, scope, statement, .shr),
.assign_bit_and => try assignOp(gz, scope, statement, .bit_and),
.assign_bit_or => try assignOp(gz, scope, statement, .bit_or),
@@ -1979,6 +1978,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
// ZIR instructions that might be a type other than `noreturn` or `void`.
.add,
.addwrap,
+ .add_sat,
.param,
.param_comptime,
.param_anytype,
@@ -2045,12 +2045,15 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.mod_rem,
.mul,
.mulwrap,
+ .mul_sat,
.ref,
.shl,
+ .shl_sat,
.shr,
.str,
.sub,
.subwrap,
+ .sub_sat,
.negate,
.negate_wrap,
.typeof,
@@ -2715,49 +2718,6 @@ fn assignOp(
_ = try gz.addBin(.store, lhs_ptr, result);
}
-fn simpleBinOpExt(
- gz: *GenZir,
- scope: *Scope,
- rl: ResultLoc,
- infix_node: Ast.Node.Index,
- lhs_node: Ast.Node.Index,
- rhs_node: Ast.Node.Index,
- tag: Zir.Inst.Extended,
- comptime T: type,
-) InnerError!Zir.Inst.Ref {
- const lhs = try expr(gz, scope, .none, lhs_node);
- const rhs = try expr(gz, scope, .none, rhs_node);
- const result = try gz.addExtendedPayload(tag, T{
- .node = gz.nodeIndexToRelative(infix_node),
- .lhs = lhs,
- .rhs = rhs,
- });
- return rvalue(gz, rl, result, infix_node);
-}
-
-fn assignOpExt(
- gz: *GenZir,
- scope: *Scope,
- infix_node: Ast.Node.Index,
- op_inst_tag: Zir.Inst.Extended,
- comptime T: type,
-) InnerError!void {
- const astgen = gz.astgen;
- const tree = astgen.tree;
- const node_datas = tree.nodes.items(.data);
-
- const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
- const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
- const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
- const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs);
- const result = try gz.addExtendedPayload(op_inst_tag, T{
- .node = gz.nodeIndexToRelative(infix_node),
- .lhs = lhs,
- .rhs = rhs,
- });
- _ = try gz.addBin(.store, lhs_ptr, result);
-}
-
fn assignShift(
gz: *GenZir,
scope: *Scope,
@@ -2781,6 +2741,24 @@ fn assignShift(
_ = try gz.addBin(.store, lhs_ptr, result);
}
+fn assignShiftSat(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!void {
+ try emitDbgNode(gz, infix_node);
+ const astgen = gz.astgen;
+ const tree = astgen.tree;
+ const node_datas = tree.nodes.items(.data);
+
+ const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
+ const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
+ // Saturating shift-left allows any integer type for both the LHS and RHS.
+ const rhs = try expr(gz, scope, .none, node_datas[infix_node].rhs);
+
+ const result = try gz.addPlNode(.shl_sat, infix_node, Zir.Inst.Bin{
+ .lhs = lhs,
+ .rhs = rhs,
+ });
+ _ = try gz.addBin(.store, lhs_ptr, result);
+}
+
fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref {
const astgen = gz.astgen;
const tree = astgen.tree;
@@ -7556,11 +7534,6 @@ fn builtinCall(
return rvalue(gz, rl, result, node);
},
- .add_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .add_with_saturation),
- .sub_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .sub_with_saturation),
- .mul_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .mul_with_saturation),
- .shl_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .shl_with_saturation),
-
.atomic_load => {
const int_type = try typeExpr(gz, scope, params[0]);
// TODO allow this pointer type to be volatile
@@ -7955,24 +7928,6 @@ fn overflowArithmetic(
return rvalue(gz, rl, result, node);
}
-fn saturatingArithmetic(
- gz: *GenZir,
- scope: *Scope,
- rl: ResultLoc,
- node: Ast.Node.Index,
- params: []const Ast.Node.Index,
- tag: Zir.Inst.Extended,
-) InnerError!Zir.Inst.Ref {
- const lhs = try expr(gz, scope, .none, params[0]);
- const rhs = try expr(gz, scope, .none, params[1]);
- const result = try gz.addExtendedPayload(tag, Zir.Inst.SaturatingArithmetic{
- .node = gz.nodeIndexToRelative(node),
- .lhs = lhs,
- .rhs = rhs,
- });
- return rvalue(gz, rl, result, node);
-}
-
fn callExpr(
gz: *GenZir,
scope: *Scope,
@@ -8198,9 +8153,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -8216,9 +8171,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
@@ -8439,9 +8394,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -8457,9 +8412,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
@@ -8619,9 +8574,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.assign,
.assign_bit_and,
.assign_bit_or,
- .assign_bit_shift_left,
- .assign_bit_shift_left_sat,
- .assign_bit_shift_right,
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
@@ -8637,9 +8592,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool {
.bang_equal,
.bit_and,
.bit_or,
- .bit_shift_left,
- .bit_shift_left_sat,
- .bit_shift_right,
+ .shl,
+ .shl_sat,
+ .shr,
.bit_xor,
.bool_and,
.bool_or,
diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig
index e415d27a3a..8f23ec86d7 100644
--- a/src/BuiltinFn.zig
+++ b/src/BuiltinFn.zig
@@ -2,7 +2,6 @@ const std = @import("std");
pub const Tag = enum {
add_with_overflow,
- add_with_saturation,
align_cast,
align_of,
as,
@@ -66,7 +65,6 @@ pub const Tag = enum {
wasm_memory_grow,
mod,
mul_with_overflow,
- mul_with_saturation,
panic,
pop_count,
ptr_cast,
@@ -81,12 +79,10 @@ pub const Tag = enum {
set_runtime_safety,
shl_exact,
shl_with_overflow,
- shl_with_saturation,
shr_exact,
shuffle,
size_of,
splat,
- sub_with_saturation,
reduce,
src,
sqrt,
@@ -531,34 +527,6 @@ pub const list = list: {
.param_count = 2,
},
},
- .{
- "@addWithSaturation",
- .{
- .tag = .add_with_saturation,
- .param_count = 2,
- },
- },
- .{
- "@subWithSaturation",
- .{
- .tag = .sub_with_saturation,
- .param_count = 2,
- },
- },
- .{
- "@mulWithSaturation",
- .{
- .tag = .mul_with_saturation,
- .param_count = 2,
- },
- },
- .{
- "@shlWithSaturation",
- .{
- .tag = .shl_with_saturation,
- .param_count = 2,
- },
- },
.{
"@memcpy",
.{
diff --git a/src/Liveness.zig b/src/Liveness.zig
index c34153b76f..93f28ad7b2 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -226,13 +226,13 @@ fn analyzeInst(
switch (inst_tags[inst]) {
.add,
.addwrap,
- .addsat,
+ .add_sat,
.sub,
.subwrap,
- .subsat,
+ .sub_sat,
.mul,
.mulwrap,
- .mulsat,
+ .mul_sat,
.div,
.rem,
.mod,
@@ -255,6 +255,7 @@ fn analyzeInst(
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_exact,
.shl_sat,
.shr,
.atomic_store_unordered,
diff --git a/src/Sema.zig b/src/Sema.zig
index be10b6d663..f106d7ea9e 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -246,7 +246,6 @@ pub fn analyzeBody(
.ptr_type_simple => try sema.zirPtrTypeSimple(block, inst),
.ref => try sema.zirRef(block, inst),
.ret_err_value_code => try sema.zirRetErrValueCode(block, inst),
- .shl => try sema.zirShl(block, inst),
.shr => try sema.zirShr(block, inst),
.slice_end => try sema.zirSliceEnd(block, inst),
.slice_sentinel => try sema.zirSliceSentinel(block, inst),
@@ -319,7 +318,6 @@ pub fn analyzeBody(
.div_exact => try sema.zirDivExact(block, inst),
.div_floor => try sema.zirDivFloor(block, inst),
.div_trunc => try sema.zirDivTrunc(block, inst),
- .shl_exact => try sema.zirShlExact(block, inst),
.shr_exact => try sema.zirShrExact(block, inst),
.bit_offset_of => try sema.zirBitOffsetOf(block, inst),
.offset_of => try sema.zirOffsetOf(block, inst),
@@ -363,14 +361,21 @@ pub fn analyzeBody(
.add => try sema.zirArithmetic(block, inst, .add),
.addwrap => try sema.zirArithmetic(block, inst, .addwrap),
+ .add_sat => try sema.zirArithmetic(block, inst, .add_sat),
.div => try sema.zirArithmetic(block, inst, .div),
.mod_rem => try sema.zirArithmetic(block, inst, .mod_rem),
.mod => try sema.zirArithmetic(block, inst, .mod),
.rem => try sema.zirArithmetic(block, inst, .rem),
.mul => try sema.zirArithmetic(block, inst, .mul),
.mulwrap => try sema.zirArithmetic(block, inst, .mulwrap),
+ .mul_sat => try sema.zirArithmetic(block, inst, .mul_sat),
.sub => try sema.zirArithmetic(block, inst, .sub),
.subwrap => try sema.zirArithmetic(block, inst, .subwrap),
+ .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
+
+ .shl => try sema.zirShl(block, inst, .shl),
+ .shl_exact => try sema.zirShl(block, inst, .shl_exact),
+ .shl_sat => try sema.zirShl(block, inst, .shl_sat),
// Instructions that we know to *always* be noreturn based solely on their tag.
// These functions match the return type of analyzeBody so that we can
@@ -694,11 +699,6 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
.c_define => return sema.zirCDefine( block, extended),
.wasm_memory_size => return sema.zirWasmMemorySize( block, extended),
.wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended),
- .add_with_saturation,
- .sub_with_saturation,
- .mul_with_saturation,
- .shl_with_saturation,
- => return sema.zirSatArithmetic( block, extended),
// zig fmt: on
}
}
@@ -5875,7 +5875,12 @@ fn zirRetErrValueCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
return sema.mod.fail(&block.base, sema.src, "TODO implement zirRetErrValueCode", .{});
}
-fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+fn zirShl(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: Zir.Inst.Index,
+ air_tag: Air.Inst.Tag,
+) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
@@ -5886,6 +5891,8 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
const lhs = sema.resolveInst(extra.lhs);
const rhs = sema.resolveInst(extra.rhs);
+ // TODO coerce rhs if air_tag is not shl_sat
+
const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
@@ -5901,6 +5908,12 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
return sema.addConstant(lhs_ty, lhs_val);
}
const val = try lhs_val.shl(rhs_val, sema.arena);
+ switch (air_tag) {
+ .shl_exact => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_exact", .{}),
+ .shl_sat => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_sat", .{}),
+ .shl => {},
+ else => unreachable,
+ }
return sema.addConstant(lhs_ty, val);
} else rs: {
if (maybe_rhs_val) |rhs_val| {
@@ -5909,8 +5922,10 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A
break :rs lhs_src;
};
+ // TODO: insert runtime safety check for shl_exact
+
try sema.requireRuntimeBlock(block, runtime_src);
- return block.addBinOp(.shl, lhs, rhs);
+ return block.addBinOp(air_tag, lhs, rhs);
}
fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -6201,105 +6216,6 @@ fn zirOverflowArithmetic(
return sema.mod.fail(&block.base, src, "TODO implement Sema.zirOverflowArithmetic", .{});
}
-fn zirSatArithmetic(
- sema: *Sema,
- block: *Scope.Block,
- extended: Zir.Inst.Extended.InstData,
-) CompileError!Air.Inst.Ref {
- const tracy = trace(@src());
- defer tracy.end();
-
- const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
- sema.src = .{ .node_offset_bin_op = extra.node };
- const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = extra.node };
- const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = extra.node };
- const lhs = sema.resolveInst(extra.lhs);
- const rhs = sema.resolveInst(extra.rhs);
-
- return sema.analyzeSatArithmetic(block, lhs, rhs, sema.src, lhs_src, rhs_src, extended);
-}
-
-fn analyzeSatArithmetic(
- sema: *Sema,
- block: *Scope.Block,
- lhs: Air.Inst.Ref,
- rhs: Air.Inst.Ref,
- src: LazySrcLoc,
- lhs_src: LazySrcLoc,
- rhs_src: LazySrcLoc,
- extended: Zir.Inst.Extended.InstData,
-) CompileError!Air.Inst.Ref {
- const lhs_ty = sema.typeOf(lhs);
- const rhs_ty = sema.typeOf(rhs);
- const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
- const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
- if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) {
- if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) {
- return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{
- lhs_ty.arrayLen(), rhs_ty.arrayLen(),
- });
- }
- return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{});
- } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) {
- return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{
- lhs_ty, rhs_ty,
- });
- }
-
- if (lhs_zig_ty_tag == .Pointer or rhs_zig_ty_tag == .Pointer)
- return sema.mod.fail(&block.base, src, "TODO implement support for pointers in zirSatArithmetic", .{});
-
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } });
- const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
- const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
-
- const scalar_type = if (resolved_type.zigTypeTag() == .Vector)
- resolved_type.elemType()
- else
- resolved_type;
-
- const scalar_tag = scalar_type.zigTypeTag();
-
- const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
-
- if (!is_int)
- return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
- @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
- });
-
- if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| {
- if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| {
- if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return sema.addConstUndef(resolved_type);
- }
- // incase rhs is 0, simply return lhs without doing any calculations
- if (rhs_val.compareWithZero(.eq)) {
- switch (extended.opcode) {
- .add_with_saturation, .sub_with_saturation => return sema.addConstant(scalar_type, lhs_val),
- else => {},
- }
- }
-
- return sema.mod.fail(&block.base, src, "TODO implement comptime saturating arithmetic for operand '{s}'", .{@tagName(extended.opcode)});
- } else {
- try sema.requireRuntimeBlock(block, rhs_src);
- }
- } else {
- try sema.requireRuntimeBlock(block, lhs_src);
- }
-
- const air_tag: Air.Inst.Tag = switch (extended.opcode) {
- .add_with_saturation => .addsat,
- .sub_with_saturation => .subsat,
- .mul_with_saturation => .mulsat,
- .shl_with_saturation => .shl_sat,
- else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for extended opcode '{s}'", .{@tagName(extended.opcode)}),
- };
-
- return block.addBinOp(air_tag, casted_lhs, casted_rhs);
-}
-
fn analyzeArithmetic(
sema: *Sema,
block: *Scope.Block,
@@ -6441,8 +6357,7 @@ fn analyzeArithmetic(
},
.addwrap => {
// Integers only; floats are checked above.
- // If either of the operands are zero, then the other operand is
- // returned, even if it is undefined.
+ // If either of the operands are zero, the other operand is returned.
// If either of the operands are undefined, the result is undefined.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
@@ -6464,6 +6379,30 @@ fn analyzeArithmetic(
} else break :rs .{ .src = lhs_src, .air_tag = .addwrap };
} else break :rs .{ .src = rhs_src, .air_tag = .addwrap };
},
+ .add_sat => {
+ // For both integers and floats:
+ // If either of the operands are zero, then the other operand is returned.
+ // If either of the operands are undefined, the result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
+ return casted_rhs;
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberAddSat(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = lhs_src, .air_tag = .add_sat };
+ } else break :rs .{ .src = rhs_src, .air_tag = .add_sat };
+ },
.sub => {
// For integers:
// If the rhs is zero, then the other operand is
@@ -6531,6 +6470,30 @@ fn analyzeArithmetic(
} else break :rs .{ .src = rhs_src, .air_tag = .subwrap };
} else break :rs .{ .src = lhs_src, .air_tag = .subwrap };
},
+ .sub_sat => {
+ // For both integers and floats:
+ // If the RHS is zero, result is LHS.
+ // If either of the operands are undefined, result is undefined.
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return casted_lhs;
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberSubSat(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat };
+ } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat };
+ },
.div => {
// For integers:
// If the lhs is zero, then zero is returned regardless of rhs.
@@ -6649,10 +6612,9 @@ fn analyzeArithmetic(
},
.mulwrap => {
// Integers only; floats are handled above.
- // If either of the operands are zero, the result is zero.
- // If either of the operands are one, the result is the other
- // operand, even if it is undefined.
- // If either of the operands are undefined, the result is undefined.
+ // If either of the operands are zero, result is zero.
+ // If either of the operands are one, result is the other operand.
+ // If either of the operands are undefined, result is undefined.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef()) {
if (lhs_val.compareWithZero(.eq)) {
@@ -6684,6 +6646,42 @@ fn analyzeArithmetic(
} else break :rs .{ .src = lhs_src, .air_tag = .mulwrap };
} else break :rs .{ .src = rhs_src, .air_tag = .mulwrap };
},
+ .mul_sat => {
+ // For both integers and floats:
+ // If either of the operands are zero, result is zero.
+ // If either of the operands are one, result is the other operand.
+ // If either of the operands are undefined, result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (lhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (lhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_rhs;
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ if (rhs_val.compareWithZero(.eq)) {
+ return sema.addConstant(scalar_type, Value.zero);
+ }
+ if (rhs_val.compare(.eq, Value.one, scalar_type)) {
+ return casted_lhs;
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.addConstUndef(scalar_type);
+ }
+ return sema.addConstant(
+ scalar_type,
+ try lhs_val.numberMulSat(rhs_val, scalar_type, sema.arena, target),
+ );
+ } else break :rs .{ .src = lhs_src, .air_tag = .mul_sat };
+ } else break :rs .{ .src = rhs_src, .air_tag = .mul_sat };
+ },
.mod_rem => {
// For integers:
// Either operand being undef is a compile error because there exists
@@ -7933,7 +7931,7 @@ fn analyzeRet(
fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
// extend this swich as additional operators are implemented
return switch (tag) {
- .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true,
+ .add, .add_sat, .sub, .sub_sat, .mul, .mul_sat, .div, .mod, .rem, .mod_rem => true,
else => false,
};
}
@@ -8600,12 +8598,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{});
}
-fn zirShlExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
- const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
- const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirShlExact", .{});
-}
-
fn zirShrExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
diff --git a/src/Zir.zig b/src/Zir.zig
index 7c171e736d..1da53a526e 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -126,6 +126,64 @@ pub const Inst = struct {
/// Twos complement wrapping integer addition.
/// Uses the `pl_node` union field. Payload is `Bin`.
addwrap,
+ /// Saturating addition.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ add_sat,
+ /// Arithmetic subtraction. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ sub,
+ /// Twos complement wrapping integer subtraction.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ subwrap,
+ /// Saturating subtraction.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ sub_sat,
+ /// Arithmetic multiplication. Asserts no integer overflow.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mul,
+ /// Twos complement wrapping integer multiplication.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mulwrap,
+ /// Saturating multiplication.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mul_sat,
+ /// Implements the `@divExact` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_exact,
+ /// Implements the `@divFloor` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_floor,
+ /// Implements the `@divTrunc` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_trunc,
+ /// Implements the `@mod` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ mod,
+ /// Implements the `@rem` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ rem,
+ /// 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.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ mod_rem,
+ /// Integer shift-left. Zeroes are shifted in from the right hand side.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ shl,
+ /// Implements the `@shlExact` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ shl_exact,
+ /// Saturating shift-left.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ shl_sat,
+ /// Integer shift-right. Arithmetic or logical depending on the signedness of
+ /// the integer type.
+ /// Uses the `pl_node` union field. Payload is `Bin`.
+ shr,
+ /// Implements the `@shrExact` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ shr_exact,
+
/// Declares a parameter of the current function. Used for:
/// * debug info
/// * checking shadowing against declarations in the current namespace
@@ -471,12 +529,6 @@ pub const Inst = struct {
/// String Literal. Makes an anonymous Decl and then takes a pointer to it.
/// Uses the `str` union field.
str,
- /// Arithmetic subtraction. Asserts no integer overflow.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- sub,
- /// Twos complement wrapping integer subtraction.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- subwrap,
/// Arithmetic negation. Asserts no integer overflow.
/// Same as sub with a lhs of 0, split into a separate instruction to save memory.
/// Uses `un_node`.
@@ -802,46 +854,6 @@ pub const Inst = struct {
/// Implements the `@bitReverse` builtin. Uses the `un_node` union field.
bit_reverse,
- /// Implements the `@divExact` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- div_exact,
- /// Implements the `@divFloor` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- div_floor,
- /// Implements the `@divTrunc` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- div_trunc,
- /// Implements the `@mod` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- mod,
- /// Implements the `@rem` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- rem,
- /// 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.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mod_rem,
- /// Arithmetic multiplication. Asserts no integer overflow.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mul,
- /// Twos complement wrapping integer multiplication.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- mulwrap,
-
- /// Integer shift-left. Zeroes are shifted in from the right hand side.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- shl,
- /// Implements the `@shlExact` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- shl_exact,
- /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type.
- /// Uses the `pl_node` union field. Payload is `Bin`.
- shr,
- /// Implements the `@shrExact` builtin.
- /// Uses the `pl_node` union field with payload `Bin`.
- shr_exact,
-
/// Implements the `@bitOffsetOf` builtin.
/// Uses the `pl_node` union field with payload `Bin`.
bit_offset_of,
@@ -961,6 +973,7 @@ pub const Inst = struct {
.param_anytype_comptime,
.add,
.addwrap,
+ .add_sat,
.alloc,
.alloc_mut,
.alloc_comptime,
@@ -1035,8 +1048,10 @@ pub const Inst = struct {
.mod_rem,
.mul,
.mulwrap,
+ .mul_sat,
.ref,
.shl,
+ .shl_sat,
.shr,
.store,
.store_node,
@@ -1045,6 +1060,7 @@ pub const Inst = struct {
.str,
.sub,
.subwrap,
+ .sub_sat,
.negate,
.negate_wrap,
.typeof,
@@ -1218,6 +1234,14 @@ pub const Inst = struct {
break :list std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{
.add = .pl_node,
.addwrap = .pl_node,
+ .add_sat = .pl_node,
+ .sub = .pl_node,
+ .subwrap = .pl_node,
+ .sub_sat = .pl_node,
+ .mul = .pl_node,
+ .mulwrap = .pl_node,
+ .mul_sat = .pl_node,
+
.param = .pl_tok,
.param_comptime = .pl_tok,
.param_anytype = .str_tok,
@@ -1297,8 +1321,6 @@ pub const Inst = struct {
.repeat_inline = .node,
.merge_error_sets = .pl_node,
.mod_rem = .pl_node,
- .mul = .pl_node,
- .mulwrap = .pl_node,
.ref = .un_tok,
.ret_node = .un_node,
.ret_load = .un_node,
@@ -1315,8 +1337,6 @@ pub const Inst = struct {
.store_to_block_ptr = .bin,
.store_to_inferred_ptr = .bin,
.str = .str,
- .sub = .pl_node,
- .subwrap = .pl_node,
.negate = .un_node,
.negate_wrap = .un_node,
.typeof = .un_node,
@@ -1437,6 +1457,7 @@ pub const Inst = struct {
.shl = .pl_node,
.shl_exact = .pl_node,
+ .shl_sat = .pl_node,
.shr = .pl_node,
.shr_exact = .pl_node,
@@ -1593,22 +1614,6 @@ pub const Inst = struct {
wasm_memory_size,
/// `operand` is payload index to `BinNode`.
wasm_memory_grow,
- /// Implements the `@addWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- add_with_saturation,
- /// Implements the `@subWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- sub_with_saturation,
- /// Implements the `@mulWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- mul_with_saturation,
- /// Implements the `@shlWithSaturation` builtin.
- /// `operand` is payload index to `SaturatingArithmetic`.
- /// `small` is unused.
- shl_with_saturation,
pub const InstData = struct {
opcode: Extended,
@@ -2788,12 +2793,6 @@ pub const Inst = struct {
ptr: Ref,
};
- pub const SaturatingArithmetic = struct {
- node: i32,
- lhs: Ref,
- rhs: Ref,
- };
-
pub const Cmpxchg = struct {
ptr: Ref,
expected_value: Ref,
diff --git a/src/codegen.zig b/src/codegen.zig
index a1f812388f..79105dc4a7 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -824,18 +824,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
switch (air_tags[inst]) {
// zig fmt: off
- .add, .ptr_add => try self.airAdd(inst),
- .addwrap => try self.airAddWrap(inst),
- .addsat => try self.airArithmeticOpSat(inst, "addsat"),
- .sub, .ptr_sub => try self.airSub(inst),
- .subwrap => try self.airSubWrap(inst),
- .subsat => try self.airArithmeticOpSat(inst, "subsat"),
- .mul => try self.airMul(inst),
- .mulwrap => try self.airMulWrap(inst),
- .mulsat => try self.airArithmeticOpSat(inst, "mulsat"),
- .div => try self.airDiv(inst),
- .rem => try self.airRem(inst),
- .mod => try self.airMod(inst),
+ .add, .ptr_add => try self.airAdd(inst),
+ .addwrap => try self.airAddWrap(inst),
+ .add_sat => try self.airAddSat(inst),
+ .sub, .ptr_sub => try self.airSub(inst),
+ .subwrap => try self.airSubWrap(inst),
+ .sub_sat => try self.airSubSat(inst),
+ .mul => try self.airMul(inst),
+ .mulwrap => try self.airMulWrap(inst),
+ .mul_sat => try self.airMulSat(inst),
+ .div => try self.airDiv(inst),
+ .rem => try self.airRem(inst),
+ .mod => try self.airMod(inst),
+ .shl, .shl_exact => try self.airShl(inst),
+ .shl_sat => try self.airShlSat(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -850,8 +852,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.bit_or => try self.airBitOr(inst),
.xor => try self.airXor(inst),
.shr => try self.airShr(inst),
- .shl => try self.airShl(inst),
- .shl_sat => try self.airArithmeticOpSat(inst, "shl_sat"),
.alloc => try self.airAlloc(inst),
.arg => try self.airArg(inst),
@@ -1306,6 +1306,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airAddSat(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airSub(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
@@ -1324,10 +1332,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
- fn airArithmeticOpSat(self: *Self, inst: Air.Inst.Index, comptime name: []const u8) !void {
+ fn airSubSat(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
- else => return self.fail("TODO implement " ++ name ++ " for {}", .{self.target.cpu.arch}),
+ else => return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch}),
};
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
@@ -1350,6 +1358,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
@@ -1412,6 +1428,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}
+ fn airShlSat(self: *Self, inst: Air.Inst.Index) !void {
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
+ }
+
fn airShr(self: *Self, inst: Air.Inst.Index) !void {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 1afa81b70f..95ce95f2e5 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -883,25 +883,27 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
// TODO use a different strategy for add that communicates to the optimizer
// that wrapping is UB.
- .add, .ptr_add => try airBinOp( f, inst, " + "),
- .addwrap => try airWrapOp(f, inst, " + ", "addw_"),
- .addsat => return f.fail("TODO: C backend: implement codegen for addsat", .{}),
+ .add, .ptr_add => try airBinOp (f, inst, " + "),
// TODO use a different strategy for sub that communicates to the optimizer
// that wrapping is UB.
- .sub, .ptr_sub => try airBinOp( f, inst, " - "),
- .subwrap => try airWrapOp(f, inst, " - ", "subw_"),
- .subsat => return f.fail("TODO: C backend: implement codegen for subsat", .{}),
+ .sub, .ptr_sub => try airBinOp (f, inst, " - "),
// TODO use a different strategy for mul that communicates to the optimizer
// that wrapping is UB.
- .mul => try airBinOp( f, inst, " * "),
- .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
- .mulsat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
+ .mul => try airBinOp (f, inst, " * "),
// TODO use a different strategy for div that communicates to the optimizer
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
.rem => try airBinOp( f, inst, " % "),
- // TODO implement modulus division
- .mod => try airBinOp( f, inst, " mod "),
+ .mod => try airBinOp( f, inst, " mod "), // TODO implement modulus division
+
+ .addwrap => try airWrapOp(f, inst, " + ", "addw_"),
+ .subwrap => try airWrapOp(f, inst, " - ", "subw_"),
+ .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"),
+
+ .add_sat => try airSatOp(f, inst, "adds_"),
+ .sub_sat => try airSatOp(f, inst, "subs_"),
+ .mul_sat => try airSatOp(f, inst, "muls_"),
+ .shl_sat => try airSatOp(f, inst, "shls_"),
.cmp_eq => try airBinOp(f, inst, " == "),
.cmp_gt => try airBinOp(f, inst, " > "),
@@ -911,18 +913,14 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.cmp_neq => try airBinOp(f, inst, " != "),
// bool_and and bool_or are non-short-circuit operations
- .bool_and => try airBinOp(f, inst, " & "),
- .bool_or => try airBinOp(f, inst, " | "),
- .bit_and => try airBinOp(f, inst, " & "),
- .bit_or => try airBinOp(f, inst, " | "),
- .xor => try airBinOp(f, inst, " ^ "),
-
- .shr => try airBinOp(f, inst, " >> "),
- .shl => try airBinOp(f, inst, " << "),
- .shl_sat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}),
-
-
- .not => try airNot( f, inst),
+ .bool_and => try airBinOp(f, inst, " & "),
+ .bool_or => try airBinOp(f, inst, " | "),
+ .bit_and => try airBinOp(f, inst, " & "),
+ .bit_or => try airBinOp(f, inst, " | "),
+ .xor => try airBinOp(f, inst, " ^ "),
+ .shr => try airBinOp(f, inst, " >> "),
+ .shl, .shl_exact => try airBinOp(f, inst, " << "),
+ .not => try airNot (f, inst),
.optional_payload => try airOptionalPayload(f, inst),
.optional_payload_ptr => try airOptionalPayload(f, inst),
@@ -1314,27 +1312,23 @@ fn airWrapOp(
return ret;
}
-fn airSatOp(
- o: *Object,
- inst: Air.Inst.Index,
- fn_op: [*:0]const u8,
-) !CValue {
- if (o.liveness.isUnused(inst))
+fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue {
+ if (f.liveness.isUnused(inst))
return CValue.none;
- const bin_op = o.air.instructions.items(.data)[inst].bin_op;
- const inst_ty = o.air.typeOfIndex(inst);
- const int_info = inst_ty.intInfo(o.dg.module.getTarget());
+ const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+ const inst_ty = f.air.typeOfIndex(inst);
+ const int_info = inst_ty.intInfo(f.object.dg.module.getTarget());
const bits = int_info.bits;
switch (bits) {
8, 16, 32, 64, 128 => {},
- else => return o.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
+ else => return f.object.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}),
}
// if it's an unsigned int with non-arbitrary bit size then we can just add
if (bits > 64) {
- return o.dg.fail("TODO: C backend: airSatOp for large integers", .{});
+ return f.object.dg.fail("TODO: C backend: airSatOp for large integers", .{});
}
var min_buf: [80]u8 = undefined;
@@ -1382,11 +1376,11 @@ fn airSatOp(
},
};
- const lhs = try o.resolveInst(bin_op.lhs);
- const rhs = try o.resolveInst(bin_op.rhs);
- const w = o.writer();
+ const lhs = try f.resolveInst(bin_op.lhs);
+ const rhs = try f.resolveInst(bin_op.rhs);
+ const w = f.object.writer();
- const ret = try o.allocLocal(inst_ty, .Mut);
+ const ret = try f.allocLocal(inst_ty, .Mut);
try w.print(" = zig_{s}", .{fn_op});
switch (inst_ty.tag()) {
@@ -1412,16 +1406,16 @@ fn airSatOp(
}
try w.writeByte('(');
- try o.writeCValue(w, lhs);
+ try f.writeCValue(w, lhs);
try w.writeAll(", ");
- try o.writeCValue(w, rhs);
+ try f.writeCValue(w, rhs);
if (int_info.signedness == .signed) {
try w.print(", {s}", .{min});
}
try w.print(", {s});", .{max});
- try o.indent_writer.insertNewline();
+ try f.object.indent_writer.insertNewline();
return ret;
}
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index cdd19146b5..b27afa9b54 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1236,27 +1236,27 @@ pub const FuncGen = struct {
for (body) |inst| {
const opt_value: ?*const llvm.Value = switch (air_tags[inst]) {
// zig fmt: off
- .add => try self.airAdd(inst, .standard),
- .addwrap => try self.airAdd(inst, .wrapping),
- .addsat => try self.airAdd(inst, .saturated),
- .sub => try self.airSub(inst, .standard),
- .subwrap => try self.airSub(inst, .wrapping),
- .subsat => try self.airSub(inst, .saturated),
- .mul => try self.airMul(inst, .standard),
- .mulwrap => try self.airMul(inst, .wrapping),
- .mulsat => try self.airMul(inst, .saturated),
- .div => try self.airDiv(inst),
- .rem => try self.airRem(inst),
- .mod => try self.airMod(inst),
- .ptr_add => try self.airPtrAdd(inst),
- .ptr_sub => try self.airPtrSub(inst),
+ .add => try self.airAdd(inst),
+ .addwrap => try self.airAddWrap(inst),
+ .add_sat => try self.airAddSat(inst),
+ .sub => try self.airSub(inst),
+ .subwrap => try self.airSubWrap(inst),
+ .sub_sat => try self.airSubSat(inst),
+ .mul => try self.airMul(inst),
+ .mulwrap => try self.airMulWrap(inst),
+ .mul_sat => try self.airMulSat(inst),
+ .div => try self.airDiv(inst),
+ .rem => try self.airRem(inst),
+ .mod => try self.airMod(inst),
+ .ptr_add => try self.airPtrAdd(inst),
+ .ptr_sub => try self.airPtrSub(inst),
+ .shl => try self.airShl(inst),
+ .shl_sat => try self.airShlSat(inst),
+ .shl_exact => try self.airShlExact(inst),
.bit_and, .bool_and => try self.airAnd(inst),
.bit_or, .bool_or => try self.airOr(inst),
.xor => try self.airXor(inst),
-
- .shl => try self.airShl(inst, false),
- .shl_sat => try self.airShl(inst, true),
.shr => try self.airShr(inst),
.cmp_eq => try self.airCmp(inst, .eq),
@@ -2028,10 +2028,8 @@ pub const FuncGen = struct {
return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{});
}
- const ArithmeticType = enum { standard, wrapping, saturated };
- fn airAdd(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
@@ -2039,21 +2037,36 @@ pub const FuncGen = struct {
const inst_ty = self.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, "");
- if (ty == .wrapping)
- return self.builder.buildAdd(lhs, rhs, "")
- else if (ty == .saturated) {
- if (inst_ty.isSignedInt())
- return self.builder.buildSAddSat(lhs, rhs, "")
- else
- return self.builder.buildUAddSat(lhs, rhs, "");
- }
if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, "");
return self.builder.buildNUWAdd(lhs, rhs, "");
}
- fn airSub(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+
+ return self.builder.buildAdd(lhs, rhs, "");
+ }
+
+ fn airAddSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.air.typeOfIndex(inst);
+
+ if (inst_ty.isAnyFloat()) return self.todo("saturating float add", .{});
+ if (inst_ty.isSignedInt()) return self.builder.buildSAddSat(lhs, rhs, "");
+
+ return self.builder.buildUAddSat(lhs, rhs, "");
+ }
+
+ fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
@@ -2061,21 +2074,35 @@ pub const FuncGen = struct {
const inst_ty = self.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, "");
- if (ty == .wrapping)
- return self.builder.buildSub(lhs, rhs, "")
- else if (ty == .saturated) {
- if (inst_ty.isSignedInt())
- return self.builder.buildSSubSat(lhs, rhs, "")
- else
- return self.builder.buildUSubSat(lhs, rhs, "");
- }
if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, "");
return self.builder.buildNUWSub(lhs, rhs, "");
}
- fn airMul(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+
+ return self.builder.buildSub(lhs, rhs, "");
+ }
+
+ fn airSubSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.air.typeOfIndex(inst);
+
+ if (inst_ty.isAnyFloat()) return self.todo("saturating float sub", .{});
+ if (inst_ty.isSignedInt()) return self.builder.buildSSubSat(lhs, rhs, "");
+ return self.builder.buildUSubSat(lhs, rhs, "");
+ }
+
+ fn airMul(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
@@ -2083,18 +2110,33 @@ pub const FuncGen = struct {
const inst_ty = self.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, "");
- if (ty == .wrapping)
- return self.builder.buildMul(lhs, rhs, "")
- else if (ty == .saturated) {
- if (inst_ty.isSignedInt())
- return self.builder.buildSMulFixSat(lhs, rhs, "")
- else
- return self.builder.buildUMulFixSat(lhs, rhs, "");
- }
if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, "");
return self.builder.buildNUWMul(lhs, rhs, "");
}
+ fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+
+ return self.builder.buildMul(lhs, rhs, "");
+ }
+
+ fn airMulSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.air.typeOfIndex(inst);
+
+ if (inst_ty.isAnyFloat()) return self.todo("saturating float mul", .{});
+ if (inst_ty.isSignedInt()) return self.builder.buildSMulFixSat(lhs, rhs, "");
+ return self.builder.buildUMulFixSat(lhs, rhs, "");
+ }
+
fn airDiv(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
@@ -2200,9 +2242,25 @@ pub const FuncGen = struct {
return self.builder.buildXor(lhs, rhs, "");
}
- fn airShl(self: *FuncGen, inst: Air.Inst.Index, sat: bool) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airShlExact(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const lhs_type = self.air.typeOf(bin_op.lhs);
+ const tg = self.dg.module.getTarget();
+ const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
+ self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
+ else
+ rhs;
+ if (lhs_type.isSignedInt()) return self.builder.buildNSWShl(lhs, casted_rhs, "");
+ return self.builder.buildNUWShl(lhs, casted_rhs, "");
+ }
+
+ fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
@@ -2212,15 +2270,25 @@ pub const FuncGen = struct {
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
- if (sat) {
- return if (lhs_type.isSignedInt())
- self.builder.buildSShlSat(lhs, casted_rhs, "")
- else
- self.builder.buildUShlSat(lhs, casted_rhs, "");
- }
return self.builder.buildShl(lhs, casted_rhs, "");
}
+ fn airShlSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const bin_op = self.air.instructions.items(.data)[inst].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const lhs_type = self.air.typeOf(bin_op.lhs);
+ const tg = self.dg.module.getTarget();
+ const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
+ self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
+ else
+ rhs;
+ if (lhs_type.isSignedInt()) return self.builder.buildSShlSat(lhs, casted_rhs, "");
+ return self.builder.buildUShlSat(lhs, casted_rhs, "");
+ }
+
fn airShr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index 178c381235..4fac6656c8 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -469,6 +469,12 @@ pub const Builder = opaque {
pub const buildShl = LLVMBuildShl;
extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+ pub const buildNUWShl = ZigLLVMBuildNUWShl;
+ extern fn ZigLLVMBuildNUWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
+ pub const buildNSWShl = ZigLLVMBuildNSWShl;
+ extern fn ZigLLVMBuildNSWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
+
pub const buildSShlSat = ZigLLVMBuildSShlSat;
extern fn ZigLLVMBuildSShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index 5c9d750729..72868e4400 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -356,9 +356,6 @@ static inline long long zig_subw_longlong(long long lhs, long long rhs, long lon
return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs));
}
-/*
- * Saturating aritmetic operations: add, sub, mul, shl
- */
#define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \
return (x > max - y) ? max : x + y; \
}
@@ -449,7 +446,7 @@ zig_shl_sat_u(u32, uint32_t, 32)
zig_shl_sat_s(i32, int32_t, 31)
zig_shl_sat_u(u64, uint64_t, 64)
zig_shl_sat_s(i64, int64_t, 63)
-zig_shl_sat_s(isize, intptr_t, 63)
-zig_shl_sat_s(short, short, 15)
-zig_shl_sat_s(int, int, 31)
-zig_shl_sat_s(long, long, 63)
+zig_shl_sat_s(isize, intptr_t, ((sizeof(intptr_t)) * CHAR_BIT - 1))
+zig_shl_sat_s(short, short, ((sizeof(short )) * CHAR_BIT - 1))
+zig_shl_sat_s(int, int, ((sizeof(int )) * CHAR_BIT - 1))
+zig_shl_sat_s(long, long, ((sizeof(long )) * CHAR_BIT - 1))
diff --git a/src/print_air.zig b/src/print_air.zig
index 7d178b52f3..885c1b62bd 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -104,13 +104,13 @@ const Writer = struct {
.add,
.addwrap,
- .addsat,
+ .add_sat,
.sub,
.subwrap,
- .subsat,
+ .sub_sat,
.mul,
.mulwrap,
- .mulsat,
+ .mul_sat,
.div,
.rem,
.mod,
@@ -133,6 +133,7 @@ const Writer = struct {
.ptr_elem_val,
.ptr_ptr_elem_val,
.shl,
+ .shl_exact,
.shl_sat,
.shr,
.set_union_tag,
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 3834a694e9..5ffd6619af 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -229,12 +229,15 @@ const Writer = struct {
.add,
.addwrap,
+ .add_sat,
.array_cat,
.array_mul,
.mul,
.mulwrap,
+ .mul_sat,
.sub,
.subwrap,
+ .sub_sat,
.cmp_lt,
.cmp_lte,
.cmp_eq,
@@ -247,6 +250,7 @@ const Writer = struct {
.mod_rem,
.shl,
.shl_exact,
+ .shl_sat,
.shr,
.shr_exact,
.xor,
@@ -400,12 +404,6 @@ const Writer = struct {
.shl_with_overflow,
=> try self.writeOverflowArithmetic(stream, extended),
- .add_with_saturation,
- .sub_with_saturation,
- .mul_with_saturation,
- .shl_with_saturation,
- => try self.writeSaturatingArithmetic(stream, extended),
-
.struct_decl => try self.writeStructDecl(stream, extended),
.union_decl => try self.writeUnionDecl(stream, extended),
.enum_decl => try self.writeEnumDecl(stream, extended),
@@ -854,18 +852,6 @@ const Writer = struct {
try self.writeSrc(stream, src);
}
- fn writeSaturatingArithmetic(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
- const extra = self.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data;
- const src: LazySrcLoc = .{ .node_offset = extra.node };
-
- try self.writeInstRef(stream, extra.lhs);
- try stream.writeAll(", ");
- try self.writeInstRef(stream, extra.rhs);
- try stream.writeAll(", ");
- try stream.writeAll(") ");
- try self.writeSrc(stream, src);
- }
-
fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index);
diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp
index e31a7015b0..5b58766df9 100644
--- a/src/stage1/all_types.hpp
+++ b/src/stage1/all_types.hpp
@@ -1818,10 +1818,6 @@ enum BuiltinFnId {
BuiltinFnIdReduce,
BuiltinFnIdMaximum,
BuiltinFnIdMinimum,
- BuiltinFnIdSatAdd,
- BuiltinFnIdSatSub,
- BuiltinFnIdSatMul,
- BuiltinFnIdSatShl,
};
struct BuiltinFnEntry {
diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp
index 14808dd0a2..8fbd02c688 100644
--- a/src/stage1/astgen.cpp
+++ b/src/stage1/astgen.cpp
@@ -4720,66 +4720,6 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast
Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMaximum, arg0_value, arg1_value, true);
return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
}
- case BuiltinFnIdSatAdd:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpAddSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
- case BuiltinFnIdSatSub:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSubSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
- case BuiltinFnIdSatMul:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMultSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
- case BuiltinFnIdSatShl:
- {
- AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
- Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope);
- if (arg0_value == ag->codegen->invalid_inst_src)
- return arg0_value;
-
- AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
- Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope);
- if (arg1_value == ag->codegen->invalid_inst_src)
- return arg1_value;
-
- Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpShlSat, arg0_value, arg1_value, true);
- return ir_lval_wrap(ag, scope, bin_op, lval, result_loc);
- }
case BuiltinFnIdMemcpy:
{
AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp
index eade843354..a0f130b79e 100644
--- a/src/stage1/codegen.cpp
+++ b/src/stage1/codegen.cpp
@@ -9134,10 +9134,6 @@ static void define_builtin_fns(CodeGen *g) {
create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2);
create_builtin_fn(g, BuiltinFnIdMaximum, "maximum", 2);
create_builtin_fn(g, BuiltinFnIdMinimum, "minimum", 2);
- create_builtin_fn(g, BuiltinFnIdSatAdd, "addWithSaturation", 2);
- create_builtin_fn(g, BuiltinFnIdSatSub, "subWithSaturation", 2);
- create_builtin_fn(g, BuiltinFnIdSatMul, "mulWithSaturation", 2);
- create_builtin_fn(g, BuiltinFnIdSatShl, "shlWithSaturation", 2);
}
static const char *bool_to_str(bool b) {
diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig
index d0fe6d1b31..dbd9367d1a 100644
--- a/src/translate_c/ast.zig
+++ b/src/translate_c/ast.zig
@@ -1462,10 +1462,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
.mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="),
.div => return renderBinOpGrouped(c, node, .div, .slash, "/"),
.div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="),
- .shl => return renderBinOpGrouped(c, node, .bit_shift_left, .angle_bracket_angle_bracket_left, "<<"),
- .shl_assign => return renderBinOp(c, node, .assign_bit_shift_left, .angle_bracket_angle_bracket_left_equal, "<<="),
- .shr => return renderBinOpGrouped(c, node, .bit_shift_right, .angle_bracket_angle_bracket_right, ">>"),
- .shr_assign => return renderBinOp(c, node, .assign_bit_shift_right, .angle_bracket_angle_bracket_right_equal, ">>="),
+ .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"),
+ .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="),
+ .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"),
+ .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="),
.mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"),
.mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="),
.@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"),
diff --git a/src/value.zig b/src/value.zig
index 29d8fa8db9..73a2b3a49f 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -1588,6 +1588,35 @@ pub const Value = extern union {
return result;
}
+ /// Supports both floats and ints; handles undefined.
+ pub fn numberAddSat(
+ lhs: Value,
+ rhs: Value,
+ ty: Type,
+ arena: *Allocator,
+ target: Target,
+ ) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+ if (ty.isAnyFloat()) {
+ // TODO: handle outside float range
+ return floatAdd(lhs, rhs, ty, arena);
+ }
+ const result = try intAdd(lhs, rhs, arena);
+
+ const max = try ty.maxInt(arena, target);
+ if (compare(result, .gt, max, ty)) {
+ return max;
+ }
+
+ const min = try ty.minInt(arena, target);
+ if (compare(result, .lt, min, ty)) {
+ return min;
+ }
+
+ return result;
+ }
+
/// Supports both floats and ints; handles undefined.
pub fn numberSubWrap(
lhs: Value,
@@ -1616,6 +1645,35 @@ pub const Value = extern union {
return result;
}
+ /// Supports both floats and ints; handles undefined.
+ pub fn numberSubSat(
+ lhs: Value,
+ rhs: Value,
+ ty: Type,
+ arena: *Allocator,
+ target: Target,
+ ) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+ if (ty.isAnyFloat()) {
+ // TODO: handle outside float range
+ return floatSub(lhs, rhs, ty, arena);
+ }
+ const result = try intSub(lhs, rhs, arena);
+
+ const max = try ty.maxInt(arena, target);
+ if (compare(result, .gt, max, ty)) {
+ return max;
+ }
+
+ const min = try ty.minInt(arena, target);
+ if (compare(result, .lt, min, ty)) {
+ return min;
+ }
+
+ return result;
+ }
+
/// Supports both floats and ints; handles undefined.
pub fn numberMulWrap(
lhs: Value,
@@ -1644,6 +1702,35 @@ pub const Value = extern union {
return result;
}
+ /// Supports both floats and ints; handles undefined.
+ pub fn numberMulSat(
+ lhs: Value,
+ rhs: Value,
+ ty: Type,
+ arena: *Allocator,
+ target: Target,
+ ) !Value {
+ if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+
+ if (ty.isAnyFloat()) {
+ // TODO: handle outside float range
+ return floatMul(lhs, rhs, ty, arena);
+ }
+ const result = try intMul(lhs, rhs, arena);
+
+ const max = try ty.maxInt(arena, target);
+ if (compare(result, .gt, max, ty)) {
+ return max;
+ }
+
+ const min = try ty.minInt(arena, target);
+ if (compare(result, .lt, min, ty)) {
+ return min;
+ }
+
+ return result;
+ }
+
/// Supports both floats and ints; handles undefined.
pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value {
if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig
index 5d7a229c3c..91f9c17fb9 100644
--- a/test/behavior/saturating_arithmetic.zig
+++ b/test/behavior/saturating_arithmetic.zig
@@ -32,7 +32,7 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void {
}
}
-test "@addWithSaturation" {
+test "saturating add" {
const S = struct {
fn doTheTest() !void {
// .{a, b, expected a+b}
@@ -50,22 +50,16 @@ test "@addWithSaturation" {
try testSaturatingOp(.add, u128, .{ maxInt(u128), 1, maxInt(u128) });
const u8x3 = std.meta.Vector(3, u8);
- try expectEqual(u8x3{ 255, 255, 255 }, @addWithSaturation(
- u8x3{ 255, 254, 1 },
- u8x3{ 1, 2, 255 },
- ));
+ try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 255, 254, 1 } +| u8x3{ 1, 2, 255 }));
const i8x3 = std.meta.Vector(3, i8);
- try expectEqual(i8x3{ 127, 127, 127 }, @addWithSaturation(
- i8x3{ 127, 126, 1 },
- i8x3{ 1, 2, 127 },
- ));
+ try expectEqual(i8x3{ 127, 127, 127 }, (i8x3{ 127, 126, 1 } +| i8x3{ 1, 2, 127 }));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
-test "@subWithSaturation" {
+test "saturating subtraction" {
const S = struct {
fn doTheTest() !void {
// .{a, b, expected a-b}
@@ -81,17 +75,14 @@ test "@subWithSaturation" {
try testSaturatingOp(.sub, u128, .{ 0, maxInt(u128), 0 });
const u8x3 = std.meta.Vector(3, u8);
- try expectEqual(u8x3{ 0, 0, 0 }, @subWithSaturation(
- u8x3{ 0, 0, 0 },
- u8x3{ 255, 255, 255 },
- ));
+ try expectEqual(u8x3{ 0, 0, 0 }, (u8x3{ 0, 0, 0 } -| u8x3{ 255, 255, 255 }));
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
-test "@mulWithSaturation" {
+test "saturating multiplication" {
// TODO: once #9660 has been solved, remove this line
if (std.builtin.target.cpu.arch == .wasm32) return error.SkipZigTest;
@@ -112,10 +103,7 @@ test "@mulWithSaturation" {
try testSaturatingOp(.mul, u128, .{ maxInt(u128), maxInt(u128), maxInt(u128) });
const u8x3 = std.meta.Vector(3, u8);
- try expectEqual(u8x3{ 255, 255, 255 }, @mulWithSaturation(
- u8x3{ 2, 2, 2 },
- u8x3{ 255, 255, 255 },
- ));
+ try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 2, 2, 2 } *| u8x3{ 255, 255, 255 }));
}
};
@@ -123,7 +111,7 @@ test "@mulWithSaturation" {
comptime try S.doTheTest();
}
-test "@shlWithSaturation" {
+test "saturating shift-left" {
const S = struct {
fn doTheTest() !void {
// .{a, b, expected a<
Date: Tue, 28 Sep 2021 20:27:28 -0700
Subject: [PATCH 146/160] saturating arithmetic supports integers only
---
src/Air.zig | 2 +-
src/Sema.zig | 14 +++++++-------
src/value.zig | 33 ++++++++++++---------------------
test/compile_errors.zig | 4 ++--
4 files changed, 22 insertions(+), 31 deletions(-)
diff --git a/src/Air.zig b/src/Air.zig
index f05c18e87a..cdc5ff2287 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -130,7 +130,7 @@ pub const Inst = struct {
/// it shifts out any bits that disagree with the resultant sign bit.
/// Uses the `bin_op` field.
shl_exact,
- /// Shift left saturating. `<<|`
+ /// Saturating integer shift left. `<<|`
/// Uses the `bin_op` field.
shl_sat,
/// Bitwise XOR. `^`
diff --git a/src/Sema.zig b/src/Sema.zig
index f106d7ea9e..0fb93f3fbe 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -6380,7 +6380,7 @@ fn analyzeArithmetic(
} else break :rs .{ .src = rhs_src, .air_tag = .addwrap };
},
.add_sat => {
- // For both integers and floats:
+ // Integers only; floats are checked above.
// If either of the operands are zero, then the other operand is returned.
// If either of the operands are undefined, the result is undefined.
if (maybe_lhs_val) |lhs_val| {
@@ -6398,7 +6398,7 @@ fn analyzeArithmetic(
if (maybe_lhs_val) |lhs_val| {
return sema.addConstant(
scalar_type,
- try lhs_val.numberAddSat(rhs_val, scalar_type, sema.arena, target),
+ try lhs_val.intAddSat(rhs_val, scalar_type, sema.arena, target),
);
} else break :rs .{ .src = lhs_src, .air_tag = .add_sat };
} else break :rs .{ .src = rhs_src, .air_tag = .add_sat };
@@ -6471,7 +6471,7 @@ fn analyzeArithmetic(
} else break :rs .{ .src = lhs_src, .air_tag = .subwrap };
},
.sub_sat => {
- // For both integers and floats:
+ // Integers only; floats are checked above.
// If the RHS is zero, result is LHS.
// If either of the operands are undefined, result is undefined.
if (maybe_rhs_val) |rhs_val| {
@@ -6489,7 +6489,7 @@ fn analyzeArithmetic(
if (maybe_rhs_val) |rhs_val| {
return sema.addConstant(
scalar_type,
- try lhs_val.numberSubSat(rhs_val, scalar_type, sema.arena, target),
+ try lhs_val.intSubSat(rhs_val, scalar_type, sema.arena, target),
);
} else break :rs .{ .src = rhs_src, .air_tag = .sub_sat };
} else break :rs .{ .src = lhs_src, .air_tag = .sub_sat };
@@ -6647,7 +6647,7 @@ fn analyzeArithmetic(
} else break :rs .{ .src = rhs_src, .air_tag = .mulwrap };
},
.mul_sat => {
- // For both integers and floats:
+ // Integers only; floats are checked above.
// If either of the operands are zero, result is zero.
// If either of the operands are one, result is the other operand.
// If either of the operands are undefined, result is undefined.
@@ -6677,7 +6677,7 @@ fn analyzeArithmetic(
}
return sema.addConstant(
scalar_type,
- try lhs_val.numberMulSat(rhs_val, scalar_type, sema.arena, target),
+ try lhs_val.intMulSat(rhs_val, scalar_type, sema.arena, target),
);
} else break :rs .{ .src = lhs_src, .air_tag = .mul_sat };
} else break :rs .{ .src = rhs_src, .air_tag = .mul_sat };
@@ -7931,7 +7931,7 @@ fn analyzeRet(
fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
// extend this swich as additional operators are implemented
return switch (tag) {
- .add, .add_sat, .sub, .sub_sat, .mul, .mul_sat, .div, .mod, .rem, .mod_rem => true,
+ .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true,
else => false,
};
}
diff --git a/src/value.zig b/src/value.zig
index 73a2b3a49f..0ead2ff1d9 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -1588,20 +1588,17 @@ pub const Value = extern union {
return result;
}
- /// Supports both floats and ints; handles undefined.
- pub fn numberAddSat(
+ /// Supports integers only; asserts neither operand is undefined.
+ pub fn intAddSat(
lhs: Value,
rhs: Value,
ty: Type,
arena: *Allocator,
target: Target,
) !Value {
- if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+ assert(!lhs.isUndef());
+ assert(!rhs.isUndef());
- if (ty.isAnyFloat()) {
- // TODO: handle outside float range
- return floatAdd(lhs, rhs, ty, arena);
- }
const result = try intAdd(lhs, rhs, arena);
const max = try ty.maxInt(arena, target);
@@ -1645,20 +1642,17 @@ pub const Value = extern union {
return result;
}
- /// Supports both floats and ints; handles undefined.
- pub fn numberSubSat(
+ /// Supports integers only; asserts neither operand is undefined.
+ pub fn intSubSat(
lhs: Value,
rhs: Value,
ty: Type,
arena: *Allocator,
target: Target,
) !Value {
- if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+ assert(!lhs.isUndef());
+ assert(!rhs.isUndef());
- if (ty.isAnyFloat()) {
- // TODO: handle outside float range
- return floatSub(lhs, rhs, ty, arena);
- }
const result = try intSub(lhs, rhs, arena);
const max = try ty.maxInt(arena, target);
@@ -1702,20 +1696,17 @@ pub const Value = extern union {
return result;
}
- /// Supports both floats and ints; handles undefined.
- pub fn numberMulSat(
+ /// Supports integers only; asserts neither operand is undefined.
+ pub fn intMulSat(
lhs: Value,
rhs: Value,
ty: Type,
arena: *Allocator,
target: Target,
) !Value {
- if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
+ assert(!lhs.isUndef());
+ assert(!rhs.isUndef());
- if (ty.isAnyFloat()) {
- // TODO: handle outside float range
- return floatMul(lhs, rhs, ty, arena);
- }
const result = try intMul(lhs, rhs, arena);
const max = try ty.maxInt(arena, target);
diff --git a/test/compile_errors.zig b/test/compile_errors.zig
index 0e59b0523f..4acd563da9 100644
--- a/test/compile_errors.zig
+++ b/test/compile_errors.zig
@@ -8859,9 +8859,9 @@ pub fn addCases(ctx: *TestContext) !void {
"tmp.zig:3:12: note: crosses namespace boundary here",
});
- ctx.objErrStage1("Issue #9619: saturating arithmetic builtins should fail to compile when given floats",
+ ctx.objErrStage1("saturating arithmetic does not allow floats",
\\pub fn main() !void {
- \\ _ = @addWithSaturation(@as(f32, 1.0), @as(f32, 1.0));
+ \\ _ = @as(f32, 1.0) +| @as(f32, 1.0);
\\}
, &[_][]const u8{
"error: invalid operands to binary expression: 'f32' and 'f32'",
From 7efc2a06264170632e56256a5fad97e945768056 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 20:33:50 -0700
Subject: [PATCH 147/160] AstGen: improved logic for nodeMayNeedMemoryLocation
* `@as` and `@bitCast` no longer unconditionally return `true` from
this function; they forward the question to their sub-expression.
* fix `@splat` incorrectly being marked as needing a memory location
(this function returns a SIMD vector; it definitely does not want a
memory location).
Makes AstGen generate slightly nicer ZIR, which in turn generates
slightly nicer AIR, generating slightly nicer machine code in debug
builds.
It also means I can procrastinate implementing the bitcast_result_ptr
ZIR instruction semantic analysis :^)
---
src/AstGen.zig | 26 ++++++++++++++++++++------
src/BuiltinFn.zig | 26 +++++++++++++++++---------
2 files changed, 37 insertions(+), 15 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 847860630a..24a73539b3 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -8271,17 +8271,31 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool
}
},
- .builtin_call,
- .builtin_call_comma,
- .builtin_call_two,
- .builtin_call_two_comma,
- => {
+ .builtin_call_two, .builtin_call_two_comma => {
const builtin_token = main_tokens[node];
const builtin_name = tree.tokenSlice(builtin_token);
// If the builtin is an invalid name, we don't cause an error here; instead
// let it pass, and the error will be "invalid builtin function" later.
const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false;
- return builtin_info.needs_mem_loc;
+ switch (builtin_info.needs_mem_loc) {
+ .never => return false,
+ .always => return true,
+ .forward1 => node = node_datas[node].rhs,
+ }
+ },
+
+ .builtin_call, .builtin_call_comma => {
+ const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs];
+ const builtin_token = main_tokens[node];
+ const builtin_name = tree.tokenSlice(builtin_token);
+ // If the builtin is an invalid name, we don't cause an error here; instead
+ // let it pass, and the error will be "invalid builtin function" later.
+ const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false;
+ switch (builtin_info.needs_mem_loc) {
+ .never => return false,
+ .always => return true,
+ .forward1 => node = params[1],
+ }
},
}
}
diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig
index 8f23ec86d7..e1f4f5bd16 100644
--- a/src/BuiltinFn.zig
+++ b/src/BuiltinFn.zig
@@ -110,10 +110,19 @@ pub const Tag = enum {
Vector,
};
+pub const MemLocRequirement = enum {
+ /// The builtin never needs a memory location.
+ never,
+ /// The builtin always needs a memory location.
+ always,
+ /// The builtin forwards the question to argument at index 1.
+ forward1,
+};
+
tag: Tag,
-/// `true` if the builtin call can take advantage of a result location pointer.
-needs_mem_loc: bool = false,
+/// Info about the builtin call's ability to take advantage of a result location pointer.
+needs_mem_loc: MemLocRequirement = .never,
/// `true` if the builtin call can be the left-hand side of an expression (assigned to).
allows_lvalue: bool = false,
/// The number of parameters to this builtin function. `null` means variable number
@@ -148,7 +157,7 @@ pub const list = list: {
"@as",
.{
.tag = .as,
- .needs_mem_loc = true,
+ .needs_mem_loc = .forward1,
.param_count = 2,
},
},
@@ -184,7 +193,7 @@ pub const list = list: {
"@bitCast",
.{
.tag = .bit_cast,
- .needs_mem_loc = true,
+ .needs_mem_loc = .forward1,
.param_count = 2,
},
},
@@ -248,7 +257,7 @@ pub const list = list: {
"@call",
.{
.tag = .call,
- .needs_mem_loc = true,
+ .needs_mem_loc = .always,
.param_count = 3,
},
},
@@ -410,7 +419,7 @@ pub const list = list: {
"@field",
.{
.tag = .field,
- .needs_mem_loc = true,
+ .needs_mem_loc = .always,
.param_count = 2,
.allows_lvalue = true,
},
@@ -699,7 +708,6 @@ pub const list = list: {
"@splat",
.{
.tag = .splat,
- .needs_mem_loc = true,
.param_count = 2,
},
},
@@ -714,7 +722,7 @@ pub const list = list: {
"@src",
.{
.tag = .src,
- .needs_mem_loc = true,
+ .needs_mem_loc = .always,
.param_count = 0,
},
},
@@ -869,7 +877,7 @@ pub const list = list: {
"@unionInit",
.{
.tag = .union_init,
- .needs_mem_loc = true,
+ .needs_mem_loc = .always,
.param_count = 3,
},
},
From 33e77f127d8237088b561fae2ca0f4412bc1d6c9 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Tue, 28 Sep 2021 22:38:51 -0700
Subject: [PATCH 148/160] stage2: implement `@clz` and `@ctz`
Also improve the LLVM backend to support lowering bigints to LLVM
values.
Moves over a bunch of math.zig test cases to the "passing for stage2"
section.
---
src/Air.zig | 10 ++
src/Liveness.zig | 2 +
src/Sema.zig | 83 +++++++++++-----
src/codegen.zig | 18 ++++
src/codegen/c.zig | 19 ++++
src/codegen/llvm.zig | 49 +++++++--
src/codegen/llvm/bindings.zig | 7 +-
src/print_air.zig | 2 +
src/type.zig | 6 +-
src/value.zig | 39 ++++++++
test/behavior/math.zig | 182 ++++++++++++++++++++++++++++++++++
test/behavior/math_stage1.zig | 178 ---------------------------------
12 files changed, 379 insertions(+), 216 deletions(-)
diff --git a/src/Air.zig b/src/Air.zig
index cdc5ff2287..f7eccfd5a5 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -160,6 +160,14 @@ pub const Inst = struct {
/// Result type is the return type of the function being called.
/// Uses the `pl_op` field with the `Call` payload. operand is the callee.
call,
+ /// Count leading zeroes of an integer according to its representation in twos complement.
+ /// Result type will always be an unsigned integer big enough to fit the answer.
+ /// Uses the `ty_op` field.
+ clz,
+ /// Count trailing zeroes of an integer according to its representation in twos complement.
+ /// Result type will always be an unsigned integer big enough to fit the answer.
+ /// Uses the `ty_op` field.
+ ctz,
/// `<`. Result type is always bool.
/// Uses the `bin_op` field.
@@ -669,6 +677,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.float_to_int,
.int_to_float,
.get_union_tag,
+ .clz,
+ .ctz,
=> return air.getRefType(datas[inst].ty_op.ty),
.loop,
diff --git a/src/Liveness.zig b/src/Liveness.zig
index 93f28ad7b2..71a0414383 100644
--- a/src/Liveness.zig
+++ b/src/Liveness.zig
@@ -304,6 +304,8 @@ fn analyzeInst(
.float_to_int,
.int_to_float,
.get_union_tag,
+ .clz,
+ .ctz,
=> {
const o = inst_datas[inst].ty_op;
return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
diff --git a/src/Sema.zig b/src/Sema.zig
index 0fb93f3fbe..51ebb496f3 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -4611,8 +4611,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs);
const operand = sema.resolveInst(extra.rhs);
- const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type);
- _ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand));
+ const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_type);
+ _ = try sema.checkIntType(block, operand_src, sema.typeOf(operand));
if (try sema.isComptimeKnown(block, operand_src, operand)) {
return sema.coerce(block, dest_type, operand, operand_src);
@@ -8384,7 +8384,7 @@ fn zirIntToFloat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile
const operand = sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
- try sema.checkIntType(block, ty_src, dest_ty);
+ _ = try sema.checkIntType(block, ty_src, dest_ty);
try sema.checkFloatType(block, operand_src, operand_ty);
if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
@@ -8493,8 +8493,8 @@ fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
const operand = sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
const mod = sema.mod;
- const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty);
- const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty);
+ const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_ty);
+ const src_is_comptime_int = try sema.checkIntType(block, operand_src, operand_ty);
if (dest_is_comptime_int) {
return sema.coerce(block, dest_ty, operand, operand_src);
@@ -8552,14 +8552,56 @@ fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE
fn zirClz(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
- const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirClz", .{});
+ const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const operand = sema.resolveInst(inst_data.operand);
+ const operand_ty = sema.typeOf(operand);
+ // TODO implement support for vectors
+ if (operand_ty.zigTypeTag() != .Int) {
+ return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{
+ operand_ty,
+ });
+ }
+ const target = sema.mod.getTarget();
+ const bits = operand_ty.intInfo(target).bits;
+ if (bits == 0) return Air.Inst.Ref.zero;
+
+ const result_ty = try Type.smallestUnsignedInt(sema.arena, bits);
+
+ const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
+ if (val.isUndef()) return sema.addConstUndef(result_ty);
+ return sema.addIntUnsigned(result_ty, val.clz(operand_ty, target));
+ } else operand_src;
+
+ try sema.requireRuntimeBlock(block, runtime_src);
+ return block.addTyOp(.clz, result_ty, operand);
}
fn zirCtz(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
- const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirCtz", .{});
+ const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
+ const operand = sema.resolveInst(inst_data.operand);
+ const operand_ty = sema.typeOf(operand);
+ // TODO implement support for vectors
+ if (operand_ty.zigTypeTag() != .Int) {
+ return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{
+ operand_ty,
+ });
+ }
+ const target = sema.mod.getTarget();
+ const bits = operand_ty.intInfo(target).bits;
+ if (bits == 0) return Air.Inst.Ref.zero;
+
+ const result_ty = try Type.smallestUnsignedInt(sema.arena, bits);
+
+ const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
+ if (val.isUndef()) return sema.addConstUndef(result_ty);
+ return sema.mod.fail(&block.base, operand_src, "TODO: implement comptime @ctz", .{});
+ } else operand_src;
+
+ try sema.requireRuntimeBlock(block, runtime_src);
+ return block.addTyOp(.ctz, result_ty, operand);
}
fn zirPopCount(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -8616,17 +8658,12 @@ fn zirOffsetOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
return sema.mod.fail(&block.base, src, "TODO: Sema.zirOffsetOf", .{});
}
-fn checkIntType(
- sema: *Sema,
- block: *Scope.Block,
- ty_src: LazySrcLoc,
- ty: Type,
-) CompileError!void {
+/// Returns `true` if the type was a comptime_int.
+fn checkIntType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!bool {
switch (ty.zigTypeTag()) {
- .ComptimeInt, .Int => {},
- else => return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{
- ty,
- }),
+ .ComptimeInt => return true,
+ .Int => return false,
+ else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}),
}
}
@@ -9416,14 +9453,6 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void
try sema.requireFunctionBlock(block, src);
}
-fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool {
- switch (ty.zigTypeTag()) {
- .ComptimeInt => return true,
- .Int => return false,
- else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}),
- }
-}
-
/// Emit a compile error if type cannot be used for a runtime variable.
fn validateVarType(
sema: *Sema,
diff --git a/src/codegen.zig b/src/codegen.zig
index 79105dc4a7..dfaedf041a 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -896,6 +896,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.memset => try self.airMemset(inst),
.set_union_tag => try self.airSetUnionTag(inst),
.get_union_tag => try self.airGetUnionTag(inst),
+ .clz => try self.airClz(inst),
+ .ctz => try self.airCtz(inst),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1606,6 +1608,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
}
+ fn airClz(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+ }
+
+ fn airCtz(self: *Self, inst: Air.Inst.Index) !void {
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
+ else => return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch}),
+ };
+ return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
+ }
+
fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
if (!self.liveness.operandDies(inst, op_index))
return false;
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 95ce95f2e5..d2ce9cc6de 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -962,6 +962,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.memcpy => try airMemcpy(f, inst),
.set_union_tag => try airSetUnionTag(f, inst),
.get_union_tag => try airGetUnionTag(f, inst),
+ .clz => try airBuiltinCall(f, inst, "clz"),
+ .ctz => try airBuiltinCall(f, inst, "ctz"),
.int_to_float,
.float_to_int,
@@ -2075,6 +2077,23 @@ fn airSimpleCast(f: *Function, inst: Air.Inst.Index) !CValue {
return local;
}
+fn airBuiltinCall(f: *Function, inst: Air.Inst.Index, fn_name: [*:0]const u8) !CValue {
+ if (f.liveness.isUnused(inst)) return CValue.none;
+
+ const inst_ty = f.air.typeOfIndex(inst);
+ const local = try f.allocLocal(inst_ty, .Const);
+ const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+ const writer = f.object.writer();
+ const operand = try f.resolveInst(ty_op.operand);
+
+ // TODO implement the function in zig.h and call it here
+
+ try writer.print(" = {s}(", .{fn_name});
+ try f.writeCValue(writer, operand);
+ try writer.writeAll(");\n");
+ return local;
+}
+
fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue {
const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index b27afa9b54..2c278221ae 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -889,14 +889,14 @@ pub const DeclGen = struct {
const llvm_type = try self.llvmType(tv.ty);
if (bigint.eqZero()) return llvm_type.constNull();
- if (bigint.limbs.len != 1) {
- return self.todo("implement bigger bigint", .{});
- }
- const llvm_int = llvm_type.constInt(bigint.limbs[0], .False);
+ const unsigned_val = if (bigint.limbs.len == 1)
+ llvm_type.constInt(bigint.limbs[0], .False)
+ else
+ llvm_type.constIntOfArbitraryPrecision(@intCast(c_uint, bigint.limbs.len), bigint.limbs.ptr);
if (!bigint.positive) {
- return llvm.constNeg(llvm_int);
+ return llvm.constNeg(unsigned_val);
}
- return llvm_int;
+ return unsigned_val;
},
.Enum => {
const llvm_type = try self.llvmType(tv.ty);
@@ -1310,6 +1310,8 @@ pub const FuncGen = struct {
.memcpy => try self.airMemcpy(inst),
.set_union_tag => try self.airSetUnionTag(inst),
.get_union_tag => try self.airGetUnionTag(inst),
+ .clz => try self.airClzCtz(inst, "ctlz"),
+ .ctz => try self.airClzCtz(inst, "cttz"),
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -2699,6 +2701,41 @@ pub const FuncGen = struct {
return self.builder.buildExtractValue(un, 1, "");
}
+ fn airClzCtz(self: *FuncGen, inst: Air.Inst.Index, prefix: [*:0]const u8) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
+
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const operand_ty = self.air.typeOf(ty_op.operand);
+ const operand = try self.resolveInst(ty_op.operand);
+ const target = self.dg.module.getTarget();
+ const bits = operand_ty.intInfo(target).bits;
+
+ var fn_name_buf: [100]u8 = undefined;
+ const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{
+ prefix, bits,
+ }) catch unreachable;
+ const llvm_i1 = self.context.intType(1);
+ const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
+ const operand_llvm_ty = try self.dg.llvmType(operand_ty);
+ const param_types = [_]*const llvm.Type{ operand_llvm_ty, llvm_i1 };
+ const fn_type = llvm.functionType(operand_llvm_ty, ¶m_types, param_types.len, .False);
+ break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
+ };
+
+ const params = [_]*const llvm.Value{ operand, llvm_i1.constNull() };
+ const wrong_size_result = self.builder.buildCall(fn_val, ¶ms, params.len, "");
+ const result_ty = self.air.typeOfIndex(inst);
+ const result_llvm_ty = try self.dg.llvmType(result_ty);
+ const result_bits = result_ty.intInfo(target).bits;
+ if (bits > result_bits) {
+ return self.builder.buildTrunc(wrong_size_result, result_llvm_ty, "");
+ } else if (bits < result_bits) {
+ return self.builder.buildZExt(wrong_size_result, result_llvm_ty, "");
+ } else {
+ return wrong_size_result;
+ }
+ }
+
fn fieldPtr(
self: *FuncGen,
inst: Air.Inst.Index,
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index 4fac6656c8..68d91f6c68 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -172,6 +172,9 @@ pub const Type = opaque {
pub const constInt = LLVMConstInt;
extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value;
+ pub const constIntOfArbitraryPrecision = LLVMConstIntOfArbitraryPrecision;
+ extern fn LLVMConstIntOfArbitraryPrecision(IntTy: *const Type, NumWords: c_uint, Words: [*]const u64) *const Value;
+
pub const constReal = LLVMConstReal;
extern fn LLVMConstReal(RealTy: *const Type, N: f64) *const Value;
@@ -300,7 +303,7 @@ extern fn LLVMGetInlineAsm(
pub const functionType = LLVMFunctionType;
extern fn LLVMFunctionType(
ReturnType: *const Type,
- ParamTypes: [*]*const Type,
+ ParamTypes: [*]const *const Type,
ParamCount: c_uint,
IsVarArg: Bool,
) *const Type;
@@ -346,7 +349,7 @@ pub const Builder = opaque {
extern fn LLVMBuildCall(
*const Builder,
Fn: *const Value,
- Args: [*]*const Value,
+ Args: [*]const *const Value,
NumArgs: c_uint,
Name: [*:0]const u8,
) *const Value;
diff --git a/src/print_air.zig b/src/print_air.zig
index 885c1b62bd..dda3b4458b 100644
--- a/src/print_air.zig
+++ b/src/print_air.zig
@@ -186,6 +186,8 @@ const Writer = struct {
.int_to_float,
.float_to_int,
.get_union_tag,
+ .clz,
+ .ctz,
=> try w.writeTyOp(s, inst),
.block,
diff --git a/src/type.zig b/src/type.zig
index e13ede852a..cb2cc6d58a 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -3902,16 +3902,16 @@ pub const Type = extern union {
const bits = bits: {
if (max == 0) break :bits 0;
const base = std.math.log2(max);
- const upper = (@as(u64, 1) << base) - 1;
+ const upper = (@as(u64, 1) << @intCast(u6, base)) - 1;
break :bits base + @boolToInt(upper < max);
};
- return switch (bits) {
+ return switch (@intCast(u16, bits)) {
1 => initTag(.u1),
8 => initTag(.u8),
16 => initTag(.u16),
32 => initTag(.u32),
64 => initTag(.u64),
- else => return Tag.int_unsigned.create(arena, bits),
+ else => |b| return Tag.int_unsigned.create(arena, b),
};
}
};
diff --git a/src/value.zig b/src/value.zig
index 0ead2ff1d9..ac52654041 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -962,6 +962,45 @@ pub const Value = extern union {
};
}
+ pub fn clz(val: Value, ty: Type, target: Target) u64 {
+ const ty_bits = ty.intInfo(target).bits;
+ switch (val.tag()) {
+ .zero, .bool_false => return ty_bits,
+ .one, .bool_true => return ty_bits - 1,
+
+ .int_u64 => {
+ const big = @clz(u64, val.castTag(.int_u64).?.data);
+ return big + ty_bits - 64;
+ },
+ .int_i64 => {
+ @panic("TODO implement i64 Value clz");
+ },
+ .int_big_positive => {
+ // TODO: move this code into std lib big ints
+ const bigint = val.castTag(.int_big_positive).?.asBigInt();
+ // Limbs are stored in little-endian order but we need
+ // to iterate big-endian.
+ var total_limb_lz: u64 = 0;
+ var i: usize = bigint.limbs.len;
+ const bits_per_limb = @sizeOf(std.math.big.Limb) * 8;
+ while (i != 0) {
+ i -= 1;
+ const limb = bigint.limbs[i];
+ const this_limb_lz = @clz(std.math.big.Limb, limb);
+ total_limb_lz += this_limb_lz;
+ if (this_limb_lz != bits_per_limb) break;
+ }
+ const total_limb_bits = bigint.limbs.len * bits_per_limb;
+ return total_limb_lz + ty_bits - total_limb_bits;
+ },
+ .int_big_negative => {
+ @panic("TODO implement int_big_negative Value clz");
+ },
+
+ else => unreachable,
+ }
+ }
+
/// Asserts the value is an integer and not undefined.
/// Returns the number of bits the value requires to represent stored in twos complement form.
pub fn intBitCountTwosComp(self: Value) usize {
diff --git a/test/behavior/math.zig b/test/behavior/math.zig
index 510cc3d438..56fbdc124d 100644
--- a/test/behavior/math.zig
+++ b/test/behavior/math.zig
@@ -53,3 +53,185 @@ fn testThreeExprInARow(f: bool, t: bool) !void {
fn assertFalse(b: bool) !void {
try expect(!b);
}
+
+test "@clz" {
+ try testClz();
+ comptime try testClz();
+}
+
+fn testClz() !void {
+ try expect(testOneClz(u8, 0b10001010) == 0);
+ try expect(testOneClz(u8, 0b00001010) == 4);
+ try expect(testOneClz(u8, 0b00011010) == 3);
+ try expect(testOneClz(u8, 0b00000000) == 8);
+ try expect(testOneClz(u128, 0xffffffffffffffff) == 64);
+ try expect(testOneClz(u128, 0x10000000000000000) == 63);
+}
+
+fn testOneClz(comptime T: type, x: T) u32 {
+ return @clz(T, x);
+}
+
+test "const number literal" {
+ const one = 1;
+ const eleven = ten + one;
+
+ try expect(eleven == 11);
+}
+const ten = 10;
+
+test "float equality" {
+ const x: f64 = 0.012;
+ const y: f64 = x + 1.0;
+
+ try testFloatEqualityImpl(x, y);
+ comptime try testFloatEqualityImpl(x, y);
+}
+
+fn testFloatEqualityImpl(x: f64, y: f64) !void {
+ const y2 = x + 1.0;
+ try expect(y == y2);
+}
+
+test "hex float literal parsing" {
+ comptime try expect(0x1.0 == 1.0);
+}
+
+test "quad hex float literal parsing in range" {
+ const a = 0x1.af23456789bbaaab347645365cdep+5;
+ const b = 0x1.dedafcff354b6ae9758763545432p-9;
+ const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534;
+ const d = 0x1.edcbff8ad76ab5bf46463233214fp-435;
+ _ = a;
+ _ = b;
+ _ = c;
+ _ = d;
+}
+
+test "underscore separator parsing" {
+ try expect(0_0_0_0 == 0);
+ try expect(1_234_567 == 1234567);
+ try expect(001_234_567 == 1234567);
+ try expect(0_0_1_2_3_4_5_6_7 == 1234567);
+
+ try expect(0b0_0_0_0 == 0);
+ try expect(0b1010_1010 == 0b10101010);
+ try expect(0b0000_1010_1010 == 0b10101010);
+ try expect(0b1_0_1_0_1_0_1_0 == 0b10101010);
+
+ try expect(0o0_0_0_0 == 0);
+ try expect(0o1010_1010 == 0o10101010);
+ try expect(0o0000_1010_1010 == 0o10101010);
+ try expect(0o1_0_1_0_1_0_1_0 == 0o10101010);
+
+ try expect(0x0_0_0_0 == 0);
+ try expect(0x1010_1010 == 0x10101010);
+ try expect(0x0000_1010_1010 == 0x10101010);
+ try expect(0x1_0_1_0_1_0_1_0 == 0x10101010);
+
+ try expect(123_456.789_000e1_0 == 123456.789000e10);
+ try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10);
+
+ try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10);
+ try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10);
+}
+
+test "hex float literal within range" {
+ const a = 0x1.0p16383;
+ const b = 0x0.1p16387;
+ const c = 0x1.0p-16382;
+ _ = a;
+ _ = b;
+ _ = c;
+}
+
+test "comptime_int addition" {
+ comptime {
+ try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950);
+ try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380);
+ }
+}
+
+test "comptime_int multiplication" {
+ comptime {
+ try expect(
+ 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567,
+ );
+ try expect(
+ 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016,
+ );
+ }
+}
+
+test "comptime_int shifting" {
+ comptime {
+ try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000);
+ }
+}
+
+test "comptime_int multi-limb shift and mask" {
+ comptime {
+ var a = 0xefffffffa0000001eeeeeeefaaaaaaab;
+
+ try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab);
+ a >>= 32;
+ try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef);
+ a >>= 32;
+ try expect(@as(u32, a & 0xffffffff) == 0xa0000001);
+ a >>= 32;
+ try expect(@as(u32, a & 0xffffffff) == 0xefffffff);
+ a >>= 32;
+
+ try expect(a == 0);
+ }
+}
+
+test "comptime_int multi-limb partial shift right" {
+ comptime {
+ var a = 0x1ffffffffeeeeeeee;
+ a >>= 16;
+ try expect(a == 0x1ffffffffeeee);
+ }
+}
+
+test "xor" {
+ try test_xor();
+ comptime try test_xor();
+}
+
+fn test_xor() !void {
+ try testOneXor(0xFF, 0x00, 0xFF);
+ try testOneXor(0xF0, 0x0F, 0xFF);
+ try testOneXor(0xFF, 0xF0, 0x0F);
+ try testOneXor(0xFF, 0x0F, 0xF0);
+ try testOneXor(0xFF, 0xFF, 0x00);
+}
+
+fn testOneXor(a: u8, b: u8, c: u8) !void {
+ try expect(a ^ b == c);
+}
+
+test "comptime_int xor" {
+ comptime {
+ try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+ try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+ try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF);
+ try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000);
+ try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000);
+ try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
+ try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF);
+ try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000);
+ }
+}
+
+test "comptime_int param and return" {
+ const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702);
+ try expect(a == 137114567242441932203689521744947848950);
+
+ const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768);
+ try expect(b == 985095453608931032642182098849559179469148836107390954364380);
+}
+
+fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int {
+ return a + b;
+}
diff --git a/test/behavior/math_stage1.zig b/test/behavior/math_stage1.zig
index 9f412930b5..f0c160ebc4 100644
--- a/test/behavior/math_stage1.zig
+++ b/test/behavior/math_stage1.zig
@@ -117,20 +117,6 @@ test "@*WithOverflow with u0 values" {
try expect(!@shlWithOverflow(u0, 0, 0, &result));
}
-test "@clz" {
- try testClz();
- comptime try testClz();
-}
-
-fn testClz() !void {
- try expect(@clz(u8, 0b10001010) == 0);
- try expect(@clz(u8, 0b00001010) == 4);
- try expect(@clz(u8, 0b00011010) == 3);
- try expect(@clz(u8, 0b00000000) == 8);
- try expect(@clz(u128, 0xffffffffffffffff) == 64);
- try expect(@clz(u128, 0x10000000000000000) == 63);
-}
-
test "@clz vectors" {
try testClzVectors();
comptime try testClzVectors();
@@ -171,14 +157,6 @@ fn testCtzVectors() !void {
try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
}
-test "const number literal" {
- const one = 1;
- const eleven = ten + one;
-
- try expect(eleven == 11);
-}
-const ten = 10;
-
test "unsigned wrapping" {
try testUnsignedWrappingEval(maxInt(u32));
comptime try testUnsignedWrappingEval(maxInt(u32));
@@ -274,19 +252,6 @@ test "small int addition" {
try expect(result == 0);
}
-test "float equality" {
- const x: f64 = 0.012;
- const y: f64 = x + 1.0;
-
- try testFloatEqualityImpl(x, y);
- comptime try testFloatEqualityImpl(x, y);
-}
-
-fn testFloatEqualityImpl(x: f64, y: f64) !void {
- const y2 = x + 1.0;
- try expect(y == y2);
-}
-
test "allow signed integer division/remainder when values are comptime known and positive or exact" {
try expect(5 / 3 == 1);
try expect(-5 / -3 == 1);
@@ -296,23 +261,6 @@ test "allow signed integer division/remainder when values are comptime known and
try expect(-6 % 3 == 0);
}
-test "hex float literal parsing" {
- comptime try expect(0x1.0 == 1.0);
-}
-
-test "quad hex float literal parsing in range" {
- const a = 0x1.af23456789bbaaab347645365cdep+5;
- const b = 0x1.dedafcff354b6ae9758763545432p-9;
- const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534;
- const d = 0x1.edcbff8ad76ab5bf46463233214fp-435;
- if (false) {
- a;
- b;
- c;
- d;
- }
-}
-
test "quad hex float literal parsing accurate" {
const a: f128 = 0x1.1111222233334444555566667777p+0;
@@ -403,45 +351,6 @@ test "quad hex float literal parsing accurate" {
comptime try S.doTheTest();
}
-test "underscore separator parsing" {
- try expect(0_0_0_0 == 0);
- try expect(1_234_567 == 1234567);
- try expect(001_234_567 == 1234567);
- try expect(0_0_1_2_3_4_5_6_7 == 1234567);
-
- try expect(0b0_0_0_0 == 0);
- try expect(0b1010_1010 == 0b10101010);
- try expect(0b0000_1010_1010 == 0b10101010);
- try expect(0b1_0_1_0_1_0_1_0 == 0b10101010);
-
- try expect(0o0_0_0_0 == 0);
- try expect(0o1010_1010 == 0o10101010);
- try expect(0o0000_1010_1010 == 0o10101010);
- try expect(0o1_0_1_0_1_0_1_0 == 0o10101010);
-
- try expect(0x0_0_0_0 == 0);
- try expect(0x1010_1010 == 0x10101010);
- try expect(0x0000_1010_1010 == 0x10101010);
- try expect(0x1_0_1_0_1_0_1_0 == 0x10101010);
-
- try expect(123_456.789_000e1_0 == 123456.789000e10);
- try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10);
-
- try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10);
- try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10);
-}
-
-test "hex float literal within range" {
- const a = 0x1.0p16383;
- const b = 0x0.1p16387;
- const c = 0x1.0p-16382;
- if (false) {
- a;
- b;
- c;
- }
-}
-
test "truncating shift left" {
try testShlTrunc(maxInt(u16));
comptime try testShlTrunc(maxInt(u16));
@@ -497,81 +406,6 @@ test "shift left/right on u0 operand" {
comptime try S.doTheTest();
}
-test "comptime_int addition" {
- comptime {
- try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950);
- try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380);
- }
-}
-
-test "comptime_int multiplication" {
- comptime {
- try expect(
- 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567,
- );
- try expect(
- 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016,
- );
- }
-}
-
-test "comptime_int shifting" {
- comptime {
- try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000);
- }
-}
-
-test "comptime_int multi-limb shift and mask" {
- comptime {
- var a = 0xefffffffa0000001eeeeeeefaaaaaaab;
-
- try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab);
- a >>= 32;
- try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef);
- a >>= 32;
- try expect(@as(u32, a & 0xffffffff) == 0xa0000001);
- a >>= 32;
- try expect(@as(u32, a & 0xffffffff) == 0xefffffff);
- a >>= 32;
-
- try expect(a == 0);
- }
-}
-
-test "comptime_int multi-limb partial shift right" {
- comptime {
- var a = 0x1ffffffffeeeeeeee;
- a >>= 16;
- try expect(a == 0x1ffffffffeeee);
- }
-}
-
-test "xor" {
- try test_xor();
- comptime try test_xor();
-}
-
-fn test_xor() !void {
- try expect(0xFF ^ 0x00 == 0xFF);
- try expect(0xF0 ^ 0x0F == 0xFF);
- try expect(0xFF ^ 0xF0 == 0x0F);
- try expect(0xFF ^ 0x0F == 0xF0);
- try expect(0xFF ^ 0xFF == 0x00);
-}
-
-test "comptime_int xor" {
- comptime {
- try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
- try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
- try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF);
- try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000);
- try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000);
- try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
- try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF);
- try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000);
- }
-}
-
test "f128" {
try test_f128();
comptime try test_f128();
@@ -757,18 +591,6 @@ fn testRound(comptime T: type, x: T) !void {
try expectEqual(x, z);
}
-test "comptime_int param and return" {
- const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702);
- try expect(a == 137114567242441932203689521744947848950);
-
- const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768);
- try expect(b == 985095453608931032642182098849559179469148836107390954364380);
-}
-
-fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int {
- return a + b;
-}
-
test "vector integer addition" {
const S = struct {
fn doTheTest() !void {
From 99961f22dca7e01b85b7819dcea814ad01b65af5 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Wed, 29 Sep 2021 00:13:21 -0700
Subject: [PATCH 149/160] stage2: enable building compiler_rt when using LLVM
backend
* AstGen: fix emitting `store_to_inferred_ptr` when it should be emitting
`store` for a variable that has an explicit alignment.
* Compilation: fix a couple memory leaks
* Sema: implement support for locals that have specified alignment.
* Sema: implement `@intCast` when it needs to emit an AIR instruction.
* Sema: implement `@alignOf`
* Implement debug printing for extended alloc ZIR instructions.
---
src/AstGen.zig | 6 +++++-
src/Compilation.zig | 9 +++++++--
src/Package.zig | 15 +++++++++------
src/Sema.zig | 47 +++++++++++++++++++++++++++++++++++++++++----
src/Zir.zig | 8 ++++++++
src/link/Elf.zig | 2 +-
src/print_zir.zig | 26 ++++++++++++++++++++++++-
src/type.zig | 1 +
8 files changed, 99 insertions(+), 15 deletions(-)
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 24a73539b3..387364cb82 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -2567,7 +2567,11 @@ fn varDecl(
for (init_scope.instructions.items) |src_inst| {
if (zir_tags[src_inst] == .store_to_block_ptr) {
if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) {
- zir_tags[src_inst] = .store_to_inferred_ptr;
+ if (var_decl.ast.type_node != 0) {
+ zir_tags[src_inst] = .store;
+ } else {
+ zir_tags[src_inst] = .store_to_inferred_ptr;
+ }
}
}
parent_zir.appendAssumeCapacity(src_inst);
diff --git a/src/Compilation.zig b/src/Compilation.zig
index ef762dae1e..cb19a21c15 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -1574,10 +1574,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
// also test the use case of `build-obj -fcompiler-rt` with the self-hosted compiler
// and make sure the compiler-rt symbols are emitted. Currently this is hooked up for
// stage1 but not stage2.
- const capable_of_building_compiler_rt = comp.bin_file.options.use_stage1;
- const capable_of_building_ssp = comp.bin_file.options.use_stage1;
+ const capable_of_building_compiler_rt = comp.bin_file.options.use_stage1 or
+ comp.bin_file.options.use_llvm;
const capable_of_building_zig_libc = comp.bin_file.options.use_stage1 or
comp.bin_file.options.use_llvm;
+ const capable_of_building_ssp = comp.bin_file.options.use_stage1;
if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) {
if (is_exe_or_dyn_lib) {
@@ -1648,6 +1649,9 @@ pub fn destroy(self: *Compilation) void {
if (self.compiler_rt_static_lib) |*crt_file| {
crt_file.deinit(gpa);
}
+ if (self.compiler_rt_obj) |*crt_file| {
+ crt_file.deinit(gpa);
+ }
if (self.libssp_static_lib) |*crt_file| {
crt_file.deinit(gpa);
}
@@ -3977,6 +3981,7 @@ fn buildOutputFromZig(
},
.root_src_path = src_basename,
};
+ defer main_pkg.deinitTable(comp.gpa);
const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len];
const target = comp.getTarget();
const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{
diff --git a/src/Package.zig b/src/Package.zig
index 3814f0eb95..f5380aaacb 100644
--- a/src/Package.zig
+++ b/src/Package.zig
@@ -99,15 +99,18 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void {
}
}
- {
- var it = pkg.table.keyIterator();
- while (it.next()) |key| {
- gpa.free(key.*);
- }
+ pkg.deinitTable(gpa);
+ gpa.destroy(pkg);
+}
+
+/// Only frees memory associated with the table.
+pub fn deinitTable(pkg: *Package, gpa: *Allocator) void {
+ var it = pkg.table.keyIterator();
+ while (it.next()) |key| {
+ gpa.free(key.*);
}
pkg.table.deinit(gpa);
- gpa.destroy(pkg);
}
pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void {
diff --git a/src/Sema.zig b/src/Sema.zig
index 51ebb496f3..53d7a9f4a2 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -1587,7 +1587,42 @@ fn zirAllocExtended(
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.AllocExtended, extended.operand);
const src: LazySrcLoc = .{ .node_offset = extra.data.src_node };
- return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended", .{});
+ const ty_src = src; // TODO better source location
+ const align_src = src; // TODO better source location
+ const small = @bitCast(Zir.Inst.AllocExtended.Small, extended.small);
+
+ var extra_index: usize = extra.end;
+
+ const var_ty: Type = if (small.has_type) blk: {
+ const type_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ break :blk try sema.resolveType(block, ty_src, type_ref);
+ } else {
+ return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended inferred", .{});
+ };
+
+ const alignment: u16 = if (small.has_align) blk: {
+ const align_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+ extra_index += 1;
+ const alignment = try sema.resolveAlign(block, align_src, align_ref);
+ break :blk alignment;
+ } else 0;
+
+ if (small.is_comptime) {
+ return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended comptime", .{});
+ }
+
+ if (!small.is_const) {
+ return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended var", .{});
+ }
+
+ const ptr_type = try Type.ptr(sema.arena, .{
+ .pointee_type = var_ty,
+ .@"align" = alignment,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
+ try sema.requireRuntimeBlock(block, src);
+ return block.addTy(.alloc, ptr_type);
}
fn zirAllocComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -4620,7 +4655,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr
return sema.mod.fail(&block.base, src, "unable to cast runtime value to 'comptime_int'", .{});
}
- return sema.mod.fail(&block.base, src, "TODO implement analyze widen or shorten int", .{});
+ try sema.requireRuntimeBlock(block, operand_src);
+ return block.addTyOp(.intcast, dest_type, operand);
}
fn zirBitcast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -8257,8 +8293,11 @@ fn zirFrameAddress(
fn zirAlignOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
- const src = inst_data.src();
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirAlignOf", .{});
+ const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+ const ty = try sema.resolveType(block, operand_src, inst_data.operand);
+ const target = sema.mod.getTarget();
+ const abi_align = ty.abiAlignment(target);
+ return sema.addIntUnsigned(Type.comptime_int, abi_align);
}
fn zirBoolToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
diff --git a/src/Zir.zig b/src/Zir.zig
index 1da53a526e..e7359f9382 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -2878,6 +2878,14 @@ pub const Inst = struct {
/// 1. align_inst: Ref, // if small 0b00X0 is set
pub const AllocExtended = struct {
src_node: i32,
+
+ pub const Small = packed struct {
+ has_type: bool,
+ has_align: bool,
+ is_const: bool,
+ is_comptime: bool,
+ _: u12 = undefined,
+ };
};
pub const Export = struct {
diff --git a/src/link/Elf.zig b/src/link/Elf.zig
index 6c3220d39e..cac475d445 100644
--- a/src/link/Elf.zig
+++ b/src/link/Elf.zig
@@ -1284,7 +1284,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt) blk: {
// TODO: remove when stage2 can build compiler_rt.zig
- if (!build_options.is_stage1 or !self.base.options.use_stage1) break :blk null;
+ if (!self.base.options.use_llvm) break :blk null;
// In the case of build-obj we include the compiler-rt symbols directly alongside
// the symbols of the root source file, in the same compilation unit.
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 5ffd6619af..54fd7e632f 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -393,6 +393,7 @@ const Writer = struct {
.@"asm" => try self.writeAsm(stream, extended),
.func => try self.writeFuncExtended(stream, extended),
.variable => try self.writeVarExtended(stream, extended),
+ .alloc => try self.writeAllocExtended(stream, extended),
.compile_log,
.typeof_peer,
@@ -423,7 +424,6 @@ const Writer = struct {
try stream.writeByte(')');
},
- .alloc,
.builtin_extern,
.wasm_memory_size,
.wasm_memory_grow,
@@ -1767,6 +1767,30 @@ const Writer = struct {
try stream.writeAll("))");
}
+ fn writeAllocExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
+ const extra = self.code.extraData(Zir.Inst.AllocExtended, extended.operand);
+ const small = @bitCast(Zir.Inst.AllocExtended.Small, extended.small);
+ const src: LazySrcLoc = .{ .node_offset = extra.data.src_node };
+
+ var extra_index: usize = extra.end;
+ const type_inst: Zir.Inst.Ref = if (!small.has_type) .none else blk: {
+ const type_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
+ extra_index += 1;
+ break :blk type_inst;
+ };
+ const align_inst: Zir.Inst.Ref = if (!small.has_align) .none else blk: {
+ const align_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
+ extra_index += 1;
+ break :blk align_inst;
+ };
+ try self.writeFlag(stream, ",is_const", small.is_const);
+ try self.writeFlag(stream, ",is_comptime", small.is_comptime);
+ try self.writeOptionalInstRef(stream, ",ty=", type_inst);
+ try self.writeOptionalInstRef(stream, ",align=", align_inst);
+ try stream.writeAll(")) ");
+ try self.writeSrc(stream, src);
+ }
+
fn writeBoolBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].bool_br;
const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);
diff --git a/src/type.zig b/src/type.zig
index cb2cc6d58a..2525aecef6 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -3866,6 +3866,7 @@ pub const Type = extern union {
};
pub const @"bool" = initTag(.bool);
+ pub const @"comptime_int" = initTag(.comptime_int);
pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {
assert(d.host_size == 0 or d.bit_offset < d.host_size * 8);
From 4e85bf11856a7fbf6072857f21a326854073b534 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Wed, 29 Sep 2021 10:46:32 -0700
Subject: [PATCH 150/160] stage2: put use_llvm and use_stage1 into the Module
cache hash
This prevents a compiler_rt built with stage2 (which is intentionally
different than when built with stage1) from being used for stage1 and
vice versa.
Fixes the regression from the previous commit.
---
src/Compilation.zig | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/Compilation.zig b/src/Compilation.zig
index cb19a21c15..76a84701ce 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -1177,6 +1177,8 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
}
hash.add(valgrind);
hash.add(single_threaded);
+ hash.add(use_stage1);
+ hash.add(use_llvm);
hash.add(dll_export_fns);
hash.add(options.is_test);
hash.add(options.skip_linker_dependencies);
From b02932f96e38732d05a6a759098cea785cb91506 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Wed, 29 Sep 2021 11:09:37 -0700
Subject: [PATCH 151/160] Sema: generic function instantiations gain addrspace
of owner Decl
---
src/Sema.zig | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Sema.zig b/src/Sema.zig
index 53d7a9f4a2..b71c4f1847 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -3281,6 +3281,7 @@ fn analyzeCall(
new_decl.is_exported = module_fn.owner_decl.is_exported;
new_decl.has_align = module_fn.owner_decl.has_align;
new_decl.has_linksection_or_addrspace = module_fn.owner_decl.has_linksection_or_addrspace;
+ new_decl.@"addrspace" = module_fn.owner_decl.@"addrspace";
new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index;
new_decl.alive = true; // This Decl is called at runtime.
new_decl.has_tv = true;
From 1d1f6a04214027da014cbc8eb780ff4c5e55f863 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Wed, 29 Sep 2021 11:33:22 -0700
Subject: [PATCH 152/160] move some behavior tests to the "passing for stage2"
section
---
test/behavior.zig | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/test/behavior.zig b/test/behavior.zig
index 479e1feffc..1855ff5cf7 100644
--- a/test/behavior.zig
+++ b/test/behavior.zig
@@ -6,6 +6,15 @@ test {
_ = @import("behavior/atomics.zig");
_ = @import("behavior/basic.zig");
_ = @import("behavior/bool.zig");
+ _ = @import("behavior/bugs/655.zig");
+ _ = @import("behavior/bugs/1277.zig");
+ _ = @import("behavior/bugs/1741.zig");
+ _ = @import("behavior/bugs/2346.zig");
+ _ = @import("behavior/bugs/2692.zig");
+ _ = @import("behavior/bugs/4769_a.zig");
+ _ = @import("behavior/bugs/4769_b.zig");
+ _ = @import("behavior/bugs/6850.zig");
+ _ = @import("behavior/bugs/9584.zig");
_ = @import("behavior/cast.zig");
_ = @import("behavior/eval.zig");
_ = @import("behavior/generics.zig");
@@ -36,11 +45,21 @@ test {
_ = @import("behavior/bit_shifting.zig");
_ = @import("behavior/bitcast.zig");
_ = @import("behavior/bitreverse.zig");
+ _ = @import("behavior/bugs/394.zig");
+ _ = @import("behavior/bugs/421.zig");
+ _ = @import("behavior/bugs/529.zig");
+ _ = @import("behavior/bugs/624.zig");
+ _ = @import("behavior/bugs/656.zig");
+ _ = @import("behavior/bugs/679.zig");
+ _ = @import("behavior/bugs/704.zig");
+ _ = @import("behavior/bugs/718.zig");
+ _ = @import("behavior/bugs/726.zig");
+ _ = @import("behavior/bugs/828.zig");
+ _ = @import("behavior/bugs/920.zig");
_ = @import("behavior/bugs/1025.zig");
_ = @import("behavior/bugs/1076.zig");
_ = @import("behavior/bugs/1111.zig");
_ = @import("behavior/bugs/1120.zig");
- _ = @import("behavior/bugs/1277.zig");
_ = @import("behavior/bugs/1310.zig");
_ = @import("behavior/bugs/1322.zig");
_ = @import("behavior/bugs/1381.zig");
@@ -50,14 +69,11 @@ test {
_ = @import("behavior/bugs/1500.zig");
_ = @import("behavior/bugs/1607.zig");
_ = @import("behavior/bugs/1735.zig");
- _ = @import("behavior/bugs/1741.zig");
_ = @import("behavior/bugs/1851.zig");
_ = @import("behavior/bugs/1914.zig");
_ = @import("behavior/bugs/2006.zig");
_ = @import("behavior/bugs/2114.zig");
- _ = @import("behavior/bugs/2346.zig");
_ = @import("behavior/bugs/2578.zig");
- _ = @import("behavior/bugs/2692.zig");
_ = @import("behavior/bugs/2889.zig");
_ = @import("behavior/bugs/3007.zig");
_ = @import("behavior/bugs/3046.zig");
@@ -69,8 +85,6 @@ test {
_ = @import("behavior/bugs/3779.zig");
_ = @import("behavior/bugs/4328.zig");
_ = @import("behavior/bugs/4560.zig");
- _ = @import("behavior/bugs/4769_a.zig");
- _ = @import("behavior/bugs/4769_b.zig");
_ = @import("behavior/bugs/4954.zig");
_ = @import("behavior/bugs/5398.zig");
_ = @import("behavior/bugs/5413.zig");
@@ -78,24 +92,10 @@ test {
_ = @import("behavior/bugs/5487.zig");
_ = @import("behavior/bugs/6456.zig");
_ = @import("behavior/bugs/6781.zig");
- _ = @import("behavior/bugs/6850.zig");
_ = @import("behavior/bugs/7027.zig");
_ = @import("behavior/bugs/7047.zig");
_ = @import("behavior/bugs/7003.zig");
_ = @import("behavior/bugs/7250.zig");
- _ = @import("behavior/bugs/9584.zig");
- _ = @import("behavior/bugs/394.zig");
- _ = @import("behavior/bugs/421.zig");
- _ = @import("behavior/bugs/529.zig");
- _ = @import("behavior/bugs/624.zig");
- _ = @import("behavior/bugs/655.zig");
- _ = @import("behavior/bugs/656.zig");
- _ = @import("behavior/bugs/679.zig");
- _ = @import("behavior/bugs/704.zig");
- _ = @import("behavior/bugs/718.zig");
- _ = @import("behavior/bugs/726.zig");
- _ = @import("behavior/bugs/828.zig");
- _ = @import("behavior/bugs/920.zig");
_ = @import("behavior/byteswap.zig");
_ = @import("behavior/byval_arg_var.zig");
_ = @import("behavior/call.zig");
From ea6706b6f406606a7523e35e34e390fb880b607e Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Wed, 29 Sep 2021 14:04:52 -0700
Subject: [PATCH 153/160] stage2: LLVM backend: implement struct type fwd decls
Makes struct types able to refer to themselves.
---
src/Module.zig | 2 +-
src/codegen/llvm.zig | 137 +++++++++++++++++++++++---------
src/codegen/llvm/bindings.zig | 17 +++-
src/type.zig | 2 +
test/behavior/struct.zig | 39 +++++++++
test/behavior/struct_stage1.zig | 39 ---------
6 files changed, 154 insertions(+), 82 deletions(-)
diff --git a/src/Module.zig b/src/Module.zig
index 83bbbb6366..6c790d3804 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -813,7 +813,7 @@ pub const Struct = struct {
is_comptime: bool,
};
- pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![]u8 {
+ pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![:0]u8 {
return s.owner_decl.getFullyQualifiedName(gpa);
}
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 2c278221ae..093ae4b8c8 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -164,9 +164,25 @@ pub const Object = struct {
/// * it works for functions not all globals.
/// Therefore, this table keeps track of the mapping.
decl_map: std.AutoHashMapUnmanaged(*const Module.Decl, *const llvm.Value),
+ /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of
+ /// the compiler, but the Type/Value memory here is backed by `type_map_arena`.
+ /// TODO we need to remove entries from this map in response to incremental compilation
+ /// but I think the frontend won't tell us about types that get deleted because
+ /// hasCodeGenBits() is false for types.
+ type_map: TypeMap,
+ /// The backing memory for `type_map`. Periodically garbage collected after flush().
+ /// The code for doing the periodical GC is not yet implemented.
+ type_map_arena: std.heap.ArenaAllocator,
/// Where to put the output object file, relative to bin_file.options.emit directory.
sub_path: []const u8,
+ pub const TypeMap = std.HashMapUnmanaged(
+ Type,
+ *const llvm.Type,
+ Type.HashContext64,
+ std.hash_map.default_max_load_percentage,
+ );
+
pub fn create(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*Object {
const obj = try gpa.create(Object);
errdefer gpa.destroy(obj);
@@ -253,6 +269,8 @@ pub const Object = struct {
.context = context,
.target_machine = target_machine,
.decl_map = .{},
+ .type_map = .{},
+ .type_map_arena = std.heap.ArenaAllocator.init(gpa),
.sub_path = sub_path,
};
}
@@ -262,6 +280,8 @@ pub const Object = struct {
self.llvm_module.dispose();
self.context.dispose();
self.decl_map.deinit(gpa);
+ self.type_map.deinit(gpa);
+ self.type_map_arena.deinit();
self.* = undefined;
}
@@ -725,10 +745,10 @@ pub const DeclGen = struct {
}
fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type {
+ const gpa = self.gpa;
log.debug("llvmType for {}", .{t});
switch (t.zigTypeTag()) {
- .Void => return self.context.voidType(),
- .NoReturn => return self.context.voidType(),
+ .Void, .NoReturn => return self.context.voidType(),
.Int => {
const info = t.intInfo(self.module.getTarget());
return self.context.intType(info.bits);
@@ -799,18 +819,38 @@ pub const DeclGen = struct {
return self.context.intType(16);
},
.Struct => {
+ const gop = try self.object.type_map.getOrPut(gpa, t);
+ if (gop.found_existing) return gop.value_ptr.*;
+
+ // The Type memory is ephemeral; since we want to store a longer-lived
+ // reference, we need to copy it here.
+ gop.key_ptr.* = try t.copy(&self.object.type_map_arena.allocator);
+
const struct_obj = t.castTag(.@"struct").?.data;
assert(struct_obj.haveFieldTypes());
- const llvm_fields = try self.gpa.alloc(*const llvm.Type, struct_obj.fields.count());
- defer self.gpa.free(llvm_fields);
- for (struct_obj.fields.values()) |field, i| {
- llvm_fields[i] = try self.llvmType(field.ty);
+
+ const name = try struct_obj.getFullyQualifiedName(gpa);
+ defer gpa.free(name);
+
+ const llvm_struct_ty = self.context.structCreateNamed(name);
+ gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
+
+ var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{};
+ try llvm_field_types.ensureTotalCapacity(gpa, struct_obj.fields.count());
+ defer llvm_field_types.deinit(gpa);
+
+ for (struct_obj.fields.values()) |field| {
+ if (!field.ty.hasCodeGenBits()) continue;
+ llvm_field_types.appendAssumeCapacity(try self.llvmType(field.ty));
}
- return self.context.structType(
- llvm_fields.ptr,
- @intCast(c_uint, llvm_fields.len),
- .False,
+
+ llvm_struct_ty.structSetBody(
+ llvm_field_types.items.ptr,
+ @intCast(c_uint, llvm_field_types.items.len),
+ llvm.Bool.fromBool(struct_obj.layout == .Packed),
);
+
+ return llvm_struct_ty;
},
.Union => {
const union_obj = t.castTag(.@"union").?.data;
@@ -838,8 +878,8 @@ pub const DeclGen = struct {
.Fn => {
const ret_ty = try self.llvmType(t.fnReturnType());
const params_len = t.fnParamLen();
- const llvm_params = try self.gpa.alloc(*const llvm.Type, params_len);
- defer self.gpa.free(llvm_params);
+ const llvm_params = try gpa.alloc(*const llvm.Type, params_len);
+ defer gpa.free(llvm_params);
for (llvm_params) |*llvm_param, i| {
llvm_param.* = try self.llvmType(t.fnParamType(i));
}
@@ -1073,21 +1113,26 @@ pub const DeclGen = struct {
return self.context.constStruct(&fields, fields.len, .False);
},
.Struct => {
- const fields_len = tv.ty.structFieldCount();
+ const llvm_struct_ty = try self.llvmType(tv.ty);
const field_vals = tv.val.castTag(.@"struct").?.data;
const gpa = self.gpa;
- const llvm_fields = try gpa.alloc(*const llvm.Value, fields_len);
- defer gpa.free(llvm_fields);
- for (llvm_fields) |*llvm_field, i| {
- llvm_field.* = try self.genTypedValue(.{
- .ty = tv.ty.structFieldType(i),
- .val = field_vals[i],
- });
+
+ var llvm_fields: std.ArrayListUnmanaged(*const llvm.Value) = .{};
+ try llvm_fields.ensureTotalCapacity(gpa, field_vals.len);
+ defer llvm_fields.deinit(gpa);
+
+ for (field_vals) |field_val, i| {
+ const field_ty = tv.ty.structFieldType(i);
+ if (!field_ty.hasCodeGenBits()) continue;
+
+ llvm_fields.appendAssumeCapacity(try self.genTypedValue(.{
+ .ty = field_ty,
+ .val = field_val,
+ }));
}
- return self.context.constStruct(
- llvm_fields.ptr,
- @intCast(c_uint, llvm_fields.len),
- .False,
+ return llvm_struct_ty.constNamedStruct(
+ llvm_fields.items.ptr,
+ @intCast(c_uint, llvm_fields.items.len),
);
},
.ComptimeInt => unreachable,
@@ -1692,13 +1737,15 @@ pub const FuncGen = struct {
const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
const struct_ptr = try self.resolveInst(struct_field.struct_operand);
const struct_ptr_ty = self.air.typeOf(struct_field.struct_operand);
- const field_index = @intCast(c_uint, struct_field.field_index);
- return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index);
+ return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, struct_field.field_index);
}
- fn airStructFieldPtrIndex(self: *FuncGen, inst: Air.Inst.Index, field_index: c_uint) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ fn airStructFieldPtrIndex(
+ self: *FuncGen,
+ inst: Air.Inst.Index,
+ field_index: u32,
+ ) !?*const llvm.Value {
+ if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const struct_ptr = try self.resolveInst(ty_op.operand);
@@ -1707,13 +1754,13 @@ pub const FuncGen = struct {
}
fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ if (self.liveness.isUnused(inst)) return null;
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
+ const struct_ty = self.air.typeOf(struct_field.struct_operand);
const struct_byval = try self.resolveInst(struct_field.struct_operand);
- const field_index = @intCast(c_uint, struct_field.field_index);
+ const field_index = llvmFieldIndex(struct_ty, struct_field.field_index);
return self.builder.buildExtractValue(struct_byval, field_index, "");
}
@@ -2643,8 +2690,7 @@ pub const FuncGen = struct {
const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value;
const target = self.dg.module.getTarget();
const dest_ptr_align = ptr_ty.ptrAlignment(target);
- const memset = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align);
- memset.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr()));
+ _ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr());
if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) {
// TODO generate valgrind client request to mark byte range as undefined
@@ -2667,14 +2713,14 @@ pub const FuncGen = struct {
const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, "");
const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr();
const target = self.dg.module.getTarget();
- const memcpy = self.builder.buildMemCpy(
+ _ = self.builder.buildMemCpy(
dest_ptr_u8,
dest_ptr_ty.ptrAlignment(target),
src_ptr_u8,
src_ptr_ty.ptrAlignment(target),
len,
+ is_volatile,
);
- memcpy.setVolatile(llvm.Bool.fromBool(is_volatile));
return null;
}
@@ -2741,11 +2787,14 @@ pub const FuncGen = struct {
inst: Air.Inst.Index,
struct_ptr: *const llvm.Value,
struct_ptr_ty: Type,
- field_index: c_uint,
+ field_index: u32,
) !?*const llvm.Value {
const struct_ty = struct_ptr_ty.childType();
switch (struct_ty.zigTypeTag()) {
- .Struct => return self.builder.buildStructGEP(struct_ptr, field_index, ""),
+ .Struct => {
+ const llvm_field_index = llvmFieldIndex(struct_ty, field_index);
+ return self.builder.buildStructGEP(struct_ptr, llvm_field_index, "");
+ },
.Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index),
else => unreachable,
}
@@ -2968,3 +3017,15 @@ fn toLlvmAtomicRmwBinOp(
.Min => if (is_signed) llvm.AtomicRMWBinOp.Min else return .UMin,
};
}
+
+/// Take into account 0 bit fields.
+fn llvmFieldIndex(ty: Type, index: u32) c_uint {
+ const struct_obj = ty.castTag(.@"struct").?.data;
+ var result: c_uint = 0;
+ for (struct_obj.fields.values()[0..index]) |field| {
+ if (field.ty.hasCodeGenBits()) {
+ result += 1;
+ }
+ }
+ return result;
+}
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index 68d91f6c68..8ded801165 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -181,6 +181,13 @@ pub const Type = opaque {
pub const constArray = LLVMConstArray;
extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value;
+ pub const constNamedStruct = LLVMConstNamedStruct;
+ extern fn LLVMConstNamedStruct(
+ StructTy: *const Type,
+ ConstantVals: [*]const *const Value,
+ Count: c_uint,
+ ) *const Value;
+
pub const getUndef = LLVMGetUndef;
extern fn LLVMGetUndef(Ty: *const Type) *const Value;
@@ -666,23 +673,25 @@ pub const Builder = opaque {
Name: [*:0]const u8,
) *const Value;
- pub const buildMemSet = LLVMBuildMemSet;
- extern fn LLVMBuildMemSet(
+ pub const buildMemSet = ZigLLVMBuildMemSet;
+ extern fn ZigLLVMBuildMemSet(
B: *const Builder,
Ptr: *const Value,
Val: *const Value,
Len: *const Value,
Align: c_uint,
+ is_volatile: bool,
) *const Value;
- pub const buildMemCpy = LLVMBuildMemCpy;
- extern fn LLVMBuildMemCpy(
+ pub const buildMemCpy = ZigLLVMBuildMemCpy;
+ extern fn ZigLLVMBuildMemCpy(
B: *const Builder,
Dst: *const Value,
DstAlign: c_uint,
Src: *const Value,
SrcAlign: c_uint,
Size: *const Value,
+ is_volatile: bool,
) *const Value;
};
diff --git a/src/type.zig b/src/type.zig
index 2525aecef6..cd7eb7a7d0 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -1785,6 +1785,8 @@ pub const Type = extern union {
if (is_packed) @panic("TODO packed structs");
var size: u64 = 0;
for (s.fields.values()) |field| {
+ if (!field.ty.hasCodeGenBits()) continue;
+
const field_align = a: {
if (field.abi_align.tag() == .abi_align_default) {
break :a field.ty.abiAlignment(target);
diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig
index 2dde3c930d..d755c92c72 100644
--- a/test/behavior/struct.zig
+++ b/test/behavior/struct.zig
@@ -90,3 +90,42 @@ test "call member function directly" {
const result = MemberFnTestFoo.member(instance);
try expect(result == 1234);
}
+
+test "struct point to self" {
+ var root: Node = undefined;
+ root.val.x = 1;
+
+ var node: Node = undefined;
+ node.next = &root;
+ node.val.x = 2;
+
+ root.next = &node;
+
+ try expect(node.next.next.next.val.x == 1);
+}
+
+test "void struct fields" {
+ const foo = VoidStructFieldsFoo{
+ .a = void{},
+ .b = 1,
+ .c = void{},
+ };
+ try expect(foo.b == 1);
+ try expect(@sizeOf(VoidStructFieldsFoo) == 4);
+}
+const VoidStructFieldsFoo = struct {
+ a: void,
+ b: i32,
+ c: void,
+};
+
+test "member functions" {
+ const r = MemberFnRand{ .seed = 1234 };
+ try expect(r.getSeed() == 1234);
+}
+const MemberFnRand = struct {
+ seed: u32,
+ pub fn getSeed(r: *const MemberFnRand) u32 {
+ return r.seed;
+ }
+};
diff --git a/test/behavior/struct_stage1.zig b/test/behavior/struct_stage1.zig
index b5394afd50..3c4aaf58ec 100644
--- a/test/behavior/struct_stage1.zig
+++ b/test/behavior/struct_stage1.zig
@@ -16,21 +16,6 @@ test "top level fields" {
try expectEqual(@as(i32, 1235), instance.top_level_field);
}
-test "void struct fields" {
- const foo = VoidStructFieldsFoo{
- .a = void{},
- .b = 1,
- .c = void{},
- };
- try expect(foo.b == 1);
- try expect(@sizeOf(VoidStructFieldsFoo) == 4);
-}
-const VoidStructFieldsFoo = struct {
- a: void,
- b: i32,
- c: void,
-};
-
const StructFoo = struct {
a: i32,
b: bool,
@@ -46,19 +31,6 @@ const Val = struct {
x: i32,
};
-test "struct point to self" {
- var root: Node = undefined;
- root.val.x = 1;
-
- var node: Node = undefined;
- node.next = &root;
- node.val.x = 2;
-
- root.next = &node;
-
- try expect(node.next.next.next.val.x == 1);
-}
-
test "fn call of struct field" {
const Foo = struct {
ptr: fn () i32,
@@ -89,17 +61,6 @@ test "store member function in variable" {
try expect(result == 1234);
}
-test "member functions" {
- const r = MemberFnRand{ .seed = 1234 };
- try expect(r.getSeed() == 1234);
-}
-const MemberFnRand = struct {
- seed: u32,
- pub fn getSeed(r: *const MemberFnRand) u32 {
- return r.seed;
- }
-};
-
test "return struct byval from function" {
const bar = makeBar2(1234, 5678);
try expect(bar.y == 5678);
From bdbedff910e18e18dc31db84a80607435e9b6ee0 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Wed, 29 Sep 2021 15:33:45 -0700
Subject: [PATCH 154/160] stage2: LLVM backend: properly set module target data
Also fix tripping LLVM assert having to do with 0 bit integers.
stage2 behavior tests now run clean in a debug build of llvm 12.
---
src/codegen/llvm.zig | 23 ++++++++++++++++-------
src/codegen/llvm/bindings.zig | 11 +++++++++++
src/type.zig | 1 +
3 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 093ae4b8c8..a1e9f47df4 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -264,6 +264,11 @@ pub const Object = struct {
);
errdefer target_machine.dispose();
+ const target_data = target_machine.createTargetDataLayout();
+ defer target_data.dispose();
+
+ llvm_module.setModuleDataLayout(target_data);
+
return Object{
.llvm_module = llvm_module,
.context = context,
@@ -1589,13 +1594,17 @@ pub const FuncGen = struct {
return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
- const operand = try self.resolveInst(ty_op.operand);
- const array_len = self.air.typeOf(ty_op.operand).elemType().arrayLen();
- const usize_llvm_ty = try self.dg.llvmType(Type.initTag(.usize));
- const len = usize_llvm_ty.constInt(array_len, .False);
+ const operand_ty = self.air.typeOf(ty_op.operand);
+ const array_ty = operand_ty.childType();
+ const llvm_usize = try self.dg.llvmType(Type.usize);
+ const len = llvm_usize.constInt(array_ty.arrayLen(), .False);
const slice_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
+ if (!array_ty.hasCodeGenBits()) {
+ return self.builder.buildInsertValue(slice_llvm_ty.getUndef(), len, 1, "");
+ }
+ const operand = try self.resolveInst(ty_op.operand);
const indices: [2]*const llvm.Value = .{
- usize_llvm_ty.constNull(), usize_llvm_ty.constNull(),
+ llvm_usize.constNull(), llvm_usize.constNull(),
};
const ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
const partial = self.builder.buildInsertValue(slice_llvm_ty.getUndef(), ptr, 0, "");
@@ -2454,12 +2463,12 @@ pub const FuncGen = struct {
}
fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
- if (self.liveness.isUnused(inst))
- return null;
+ if (self.liveness.isUnused(inst)) return null;
// buildAlloca expects the pointee type, not the pointer type, so assert that
// a Payload.PointerSimple is passed to the alloc instruction.
const ptr_ty = self.air.typeOfIndex(inst);
const pointee_type = ptr_ty.elemType();
+ if (!pointee_type.hasCodeGenBits()) return null;
const pointee_llvm_ty = try self.dg.llvmType(pointee_type);
const target = self.dg.module.getTarget();
const alloca_inst = self.buildAlloca(pointee_llvm_ty);
diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig
index 8ded801165..69845c0899 100644
--- a/src/codegen/llvm/bindings.zig
+++ b/src/codegen/llvm/bindings.zig
@@ -219,6 +219,9 @@ pub const Module = opaque {
pub const verify = LLVMVerifyModule;
extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) Bool;
+ pub const setModuleDataLayout = LLVMSetModuleDataLayout;
+ extern fn LLVMSetModuleDataLayout(*const Module, *const TargetData) void;
+
pub const addFunction = LLVMAddFunction;
extern fn LLVMAddFunction(*const Module, Name: [*:0]const u8, FunctionTy: *const Type) *const Value;
@@ -766,6 +769,14 @@ pub const TargetMachine = opaque {
llvm_ir_filename: ?[*:0]const u8,
bitcode_filename: ?[*:0]const u8,
) bool;
+
+ pub const createTargetDataLayout = LLVMCreateTargetDataLayout;
+ extern fn LLVMCreateTargetDataLayout(*const TargetMachine) *const TargetData;
+};
+
+pub const TargetData = opaque {
+ pub const dispose = LLVMDisposeTargetData;
+ extern fn LLVMDisposeTargetData(*const TargetData) void;
};
pub const CodeModel = enum(c_int) {
diff --git a/src/type.zig b/src/type.zig
index cd7eb7a7d0..dacde84167 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -3868,6 +3868,7 @@ pub const Type = extern union {
};
pub const @"bool" = initTag(.bool);
+ pub const @"usize" = initTag(.usize);
pub const @"comptime_int" = initTag(.comptime_int);
pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {
From ba7f40c4302fe88fddc92b5b44365f74e00800ff Mon Sep 17 00:00:00 2001
From: Andrew Kelley
Date: Wed, 29 Sep 2021 15:37:34 -0700
Subject: [PATCH 155/160] stage2: fix ELF linking to include compiler_rt
There was duplicated logic for whether to include compiler_rt in the
linker line both in the frontend and in the linker backends. Now the
logic is only in the frontend; the linker puts it on the linker line if
the frontend provides it.
Fixes the CI failures.
---
src/link/Elf.zig | 19 +++++--------------
1 file changed, 5 insertions(+), 14 deletions(-)
diff --git a/src/link/Elf.zig b/src/link/Elf.zig
index cac475d445..a8efa8dab9 100644
--- a/src/link/Elf.zig
+++ b/src/link/Elf.zig
@@ -1282,20 +1282,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
const gc_sections = self.base.options.gc_sections orelse !is_obj;
const stack_size = self.base.options.stack_size_override orelse 16777216;
const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
- const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt) blk: {
- // TODO: remove when stage2 can build compiler_rt.zig
- if (!self.base.options.use_llvm) break :blk null;
-
- // In the case of build-obj we include the compiler-rt symbols directly alongside
- // the symbols of the root source file, in the same compilation unit.
- if (is_obj) break :blk null;
-
- if (is_exe_or_dyn_lib) {
- break :blk comp.compiler_rt_static_lib.?.full_object_path;
- } else {
- break :blk comp.compiler_rt_obj.?.full_object_path;
- }
- } else null;
+ const compiler_rt_path: ?[]const u8 = blk: {
+ if (comp.compiler_rt_static_lib) |x| break :blk x.full_object_path;
+ if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
+ break :blk null;
+ };
// Here we want to determine whether we can save time by not invoking LLD when the
// output is unchanged. None of the linker options or the object files that are being
From 2ed9288246821c39ae75fa21998a53b34e713cd4 Mon Sep 17 00:00:00 2001
From: Matthew Borkowski
Date: Thu, 30 Sep 2021 01:55:21 -0400
Subject: [PATCH 156/160] parse.zig: better c pointer prefix parsing, don't
index out of bounds on eof
---
lib/std/zig/parse.zig | 17 ++++++-----------
lib/std/zig/parser_test.zig | 8 ++++++++
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
index 021b028455..a449f6ae0f 100644
--- a/lib/std/zig/parse.zig
+++ b/lib/std/zig/parse.zig
@@ -1584,18 +1584,13 @@ const Parser = struct {
_ = p.nextToken();
const asterisk = p.nextToken();
var sentinel: Node.Index = 0;
- prefix: {
- if (p.eatToken(.identifier)) |ident| {
- const token_slice = p.source[p.token_starts[ident]..][0..2];
- if (!std.mem.eql(u8, token_slice, "c]")) {
- p.tok_i -= 1;
- } else {
- break :prefix;
- }
- }
- if (p.eatToken(.colon)) |_| {
- sentinel = try p.expectExpr();
+ if (p.eatToken(.identifier)) |ident| {
+ const ident_slice = p.source[p.token_starts[ident]..p.token_starts[ident + 1]];
+ if (!std.mem.eql(u8, std.mem.trimRight(u8, ident_slice, &std.ascii.spaces), "c")) {
+ p.tok_i -= 1;
}
+ } else if (p.eatToken(.colon)) |_| {
+ sentinel = try p.expectExpr();
}
_ = try p.expectToken(.r_bracket);
const mods = try p.parsePtrModifiers();
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index 57f081decb..f69f0598dd 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -5259,6 +5259,14 @@ test "recovery: nonfinal varargs" {
});
}
+test "recovery: eof in c pointer" {
+ try testError(
+ \\const Ptr = [*c
+ , &[_]Error{
+ .expected_token,
+ });
+}
+
const std = @import("std");
const mem = std.mem;
const print = std.debug.print;
From f87156e33c688effcf00b8fa9c2542391423ee78 Mon Sep 17 00:00:00 2001
From: Martin Wickham
Date: Wed, 29 Sep 2021 18:37:12 -0500
Subject: [PATCH 157/160] Add a panic handler to give better errors for crashes
in sema
---
lib/std/Thread/Mutex.zig | 177 ++++++------
src/Sema.zig | 6 +
src/crash_report.zig | 581 +++++++++++++++++++++++++++++++++++++++
src/main.zig | 6 +
src/print_zir.zig | 342 +++++++++++++----------
5 files changed, 893 insertions(+), 219 deletions(-)
create mode 100644 src/crash_report.zig
diff --git a/lib/std/Thread/Mutex.zig b/lib/std/Thread/Mutex.zig
index a337809a18..7473d9ec7f 100644
--- a/lib/std/Thread/Mutex.zig
+++ b/lib/std/Thread/Mutex.zig
@@ -33,17 +33,29 @@ const testing = std.testing;
const StaticResetEvent = std.thread.StaticResetEvent;
/// Try to acquire the mutex without blocking. Returns `null` if the mutex is
-/// unavailable. Otherwise returns `Held`. Call `release` on `Held`.
-pub fn tryAcquire(m: *Mutex) ?Impl.Held {
+/// unavailable. Otherwise returns `Held`. Call `release` on `Held`, or use
+/// releaseDirect().
+pub fn tryAcquire(m: *Mutex) ?Held {
return m.impl.tryAcquire();
}
/// Acquire the mutex. Deadlocks if the mutex is already
/// held by the calling thread.
-pub fn acquire(m: *Mutex) Impl.Held {
+pub fn acquire(m: *Mutex) Held {
return m.impl.acquire();
}
+/// Release the mutex. Prefer Held.release() if available.
+pub fn releaseDirect(m: *Mutex) void {
+ return m.impl.releaseDirect();
+}
+
+/// A held mutex handle. Call release to allow other threads to
+/// take the mutex. Do not call release() more than once.
+/// For more complex scenarios, this handle can be discarded
+/// and Mutex.releaseDirect can be called instead.
+pub const Held = Impl.Held;
+
const Impl = if (builtin.single_threaded)
Dummy
else if (builtin.os.tag == .windows)
@@ -53,6 +65,32 @@ else if (std.Thread.use_pthreads)
else
AtomicMutex;
+fn HeldInterface(comptime MutexType: type) type {
+ return struct {
+ const Mixin = @This();
+ pub const Held = struct {
+ mutex: *MutexType,
+
+ pub fn release(held: Mixin.Held) void {
+ held.mutex.releaseDirect();
+ }
+ };
+
+ pub fn tryAcquire(m: *MutexType) ?Mixin.Held {
+ if (m.tryAcquireDirect()) {
+ return Mixin.Held{ .mutex = m };
+ } else {
+ return null;
+ }
+ }
+
+ pub fn acquire(m: *MutexType) Mixin.Held {
+ m.acquireDirect();
+ return Mixin.Held{ .mutex = m };
+ }
+ };
+}
+
pub const AtomicMutex = struct {
state: State = .unlocked,
@@ -62,39 +100,32 @@ pub const AtomicMutex = struct {
waiting,
};
- pub const Held = struct {
- mutex: *AtomicMutex,
+ pub usingnamespace HeldInterface(@This());
- pub fn release(held: Held) void {
- switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) {
- .unlocked => unreachable,
- .locked => {},
- .waiting => held.mutex.unlockSlow(),
- }
- }
- };
-
- pub fn tryAcquire(m: *AtomicMutex) ?Held {
- if (@cmpxchgStrong(
+ fn tryAcquireDirect(m: *AtomicMutex) bool {
+ return @cmpxchgStrong(
State,
&m.state,
.unlocked,
.locked,
.Acquire,
.Monotonic,
- ) == null) {
- return Held{ .mutex = m };
- } else {
- return null;
- }
+ ) == null;
}
- pub fn acquire(m: *AtomicMutex) Held {
+ fn acquireDirect(m: *AtomicMutex) void {
switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) {
.unlocked => {},
else => |s| m.lockSlow(s),
}
- return Held{ .mutex = m };
+ }
+
+ fn releaseDirect(m: *AtomicMutex) void {
+ switch (@atomicRmw(State, &m.state, .Xchg, .unlocked, .Release)) {
+ .unlocked => unreachable,
+ .locked => {},
+ .waiting => m.unlockSlow(),
+ }
}
fn lockSlow(m: *AtomicMutex, current_state: State) void {
@@ -171,36 +202,20 @@ pub const AtomicMutex = struct {
pub const PthreadMutex = struct {
pthread_mutex: std.c.pthread_mutex_t = .{},
- pub const Held = struct {
- mutex: *PthreadMutex,
+ pub usingnamespace HeldInterface(@This());
- pub fn release(held: Held) void {
- switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) {
- .SUCCESS => return,
- .INVAL => unreachable,
- .AGAIN => unreachable,
- .PERM => unreachable,
- else => unreachable,
- }
- }
- };
-
- /// Try to acquire the mutex without blocking. Returns null if
- /// the mutex is unavailable. Otherwise returns Held. Call
- /// release on Held.
- pub fn tryAcquire(m: *PthreadMutex) ?Held {
- if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == .SUCCESS) {
- return Held{ .mutex = m };
- } else {
- return null;
- }
+ /// Try to acquire the mutex without blocking. Returns true if
+ /// the mutex is unavailable. Otherwise returns false. Call
+ /// release when done.
+ fn tryAcquireDirect(m: *PthreadMutex) bool {
+ return std.c.pthread_mutex_trylock(&m.pthread_mutex) == .SUCCESS;
}
/// Acquire the mutex. Will deadlock if the mutex is already
/// held by the calling thread.
- pub fn acquire(m: *PthreadMutex) Held {
+ fn acquireDirect(m: *PthreadMutex) void {
switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) {
- .SUCCESS => return Held{ .mutex = m },
+ .SUCCESS => {},
.INVAL => unreachable,
.BUSY => unreachable,
.AGAIN => unreachable,
@@ -209,6 +224,16 @@ pub const PthreadMutex = struct {
else => unreachable,
}
}
+
+ fn releaseDirect(m: *PthreadMutex) void {
+ switch (std.c.pthread_mutex_unlock(&m.pthread_mutex)) {
+ .SUCCESS => return,
+ .INVAL => unreachable,
+ .AGAIN => unreachable,
+ .PERM => unreachable,
+ else => unreachable,
+ }
+ }
};
/// This has the sematics as `Mutex`, however it does not actually do any
@@ -216,58 +241,50 @@ pub const PthreadMutex = struct {
pub const Dummy = struct {
lock: @TypeOf(lock_init) = lock_init,
+ pub usingnamespace HeldInterface(@This());
+
const lock_init = if (std.debug.runtime_safety) false else {};
- pub const Held = struct {
- mutex: *Dummy,
-
- pub fn release(held: Held) void {
- if (std.debug.runtime_safety) {
- held.mutex.lock = false;
- }
- }
- };
-
- /// Try to acquire the mutex without blocking. Returns null if
- /// the mutex is unavailable. Otherwise returns Held. Call
- /// release on Held.
- pub fn tryAcquire(m: *Dummy) ?Held {
+ /// Try to acquire the mutex without blocking. Returns false if
+ /// the mutex is unavailable. Otherwise returns true.
+ fn tryAcquireDirect(m: *Dummy) bool {
if (std.debug.runtime_safety) {
- if (m.lock) return null;
+ if (m.lock) return false;
m.lock = true;
}
- return Held{ .mutex = m };
+ return true;
}
/// Acquire the mutex. Will deadlock if the mutex is already
/// held by the calling thread.
- pub fn acquire(m: *Dummy) Held {
- return m.tryAcquire() orelse @panic("deadlock detected");
+ fn acquireDirect(m: *Dummy) void {
+ if (!m.tryAcquireDirect()) {
+ @panic("deadlock detected");
+ }
+ }
+
+ fn releaseDirect(m: *Dummy) void {
+ if (std.debug.runtime_safety) {
+ m.lock = false;
+ }
}
};
const WindowsMutex = struct {
srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT,
- pub const Held = struct {
- mutex: *WindowsMutex,
+ pub usingnamespace HeldInterface(@This());
- pub fn release(held: Held) void {
- windows.kernel32.ReleaseSRWLockExclusive(&held.mutex.srwlock);
- }
- };
-
- pub fn tryAcquire(m: *WindowsMutex) ?Held {
- if (windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) {
- return Held{ .mutex = m };
- } else {
- return null;
- }
+ fn tryAcquireDirect(m: *WindowsMutex) bool {
+ return windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE;
}
- pub fn acquire(m: *WindowsMutex) Held {
+ fn acquireDirect(m: *WindowsMutex) void {
windows.kernel32.AcquireSRWLockExclusive(&m.srwlock);
- return Held{ .mutex = m };
+ }
+
+ fn releaseDirect(m: *WindowsMutex) void {
+ windows.kernel32.ReleaseSRWLockExclusive(&m.srwlock);
}
};
diff --git a/src/Sema.zig b/src/Sema.zig
index b71c4f1847..f9f0160064 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -90,6 +90,7 @@ const LazySrcLoc = Module.LazySrcLoc;
const RangeSet = @import("RangeSet.zig");
const target_util = @import("target.zig");
const Package = @import("Package.zig");
+const crash_report = @import("crash_report.zig");
pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref);
@@ -153,11 +154,16 @@ pub fn analyzeBody(
var orig_captures: usize = parent_capture_scope.captures.count();
+ var crash_info = crash_report.prepAnalyzeBody(sema, block, body);
+ crash_info.push();
+ defer crash_info.pop();
+
// We use a while(true) loop here to avoid a redundant way of breaking out of
// the loop. The only way to break out of the loop is with a `noreturn`
// instruction.
var i: usize = 0;
const result = while (true) {
+ crash_info.setBodyIndex(i);
const inst = body[i];
const air_inst: Air.Inst.Ref = switch (tags[inst]) {
// zig fmt: off
diff --git a/src/crash_report.zig b/src/crash_report.zig
new file mode 100644
index 0000000000..84f4b8db84
--- /dev/null
+++ b/src/crash_report.zig
@@ -0,0 +1,581 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const debug = std.debug;
+const os = std.os;
+const io = std.io;
+const print_zir = @import("print_zir.zig");
+
+const Module = @import("Module.zig");
+const Sema = @import("Sema.zig");
+const Zir = @import("Zir.zig");
+
+pub const is_enabled = builtin.mode == .Debug;
+
+/// To use these crash report diagnostics, publish these symbols in your main file.
+/// You will also need to call initialize() on startup, preferably as the very first operation in your program.
+pub const root_decls = struct {
+ pub const panic = if (is_enabled) compilerPanic else std.builtin.default_panic;
+ pub const enable_segfault_handler = if (is_enabled) false else debug.default_enable_segfault_handler;
+};
+
+/// Install signal handlers to identify crashes and report diagnostics.
+pub fn initialize() void {
+ if (is_enabled and debug.have_segfault_handling_support) {
+ attachSegfaultHandler();
+ }
+}
+
+fn En(comptime T: type) type {
+ return if (is_enabled) T else void;
+}
+
+fn en(val: anytype) En(@TypeOf(val)) {
+ return if (is_enabled) val else {};
+}
+
+pub const AnalyzeBody = struct {
+ parent: if (is_enabled) ?*AnalyzeBody else void,
+ sema: En(*Sema),
+ block: En(*Module.Scope.Block),
+ body: En([]const Zir.Inst.Index),
+ body_index: En(usize),
+
+ pub fn push(self: *@This()) void {
+ if (!is_enabled) return;
+ const head = &zir_state;
+ debug.assert(self.parent == null);
+ self.parent = head.*;
+ head.* = self;
+ }
+
+ pub fn pop(self: *@This()) void {
+ if (!is_enabled) return;
+ const head = &zir_state;
+ const old = head.*.?;
+ debug.assert(old == self);
+ head.* = old.parent;
+ }
+
+ pub fn setBodyIndex(self: *@This(), index: usize) void {
+ if (!is_enabled) return;
+ self.body_index = index;
+ }
+};
+
+threadlocal var zir_state: ?*AnalyzeBody = if (is_enabled) null else @compileError("Cannot use zir_state if crash_report is disabled.");
+
+pub fn prepAnalyzeBody(sema: *Sema, block: *Module.Scope.Block, body: []const Zir.Inst.Index) AnalyzeBody {
+ if (is_enabled) {
+ return .{
+ .parent = null,
+ .sema = sema,
+ .block = block,
+ .body = body,
+ .body_index = 0,
+ };
+ } else {
+ if (@sizeOf(AnalyzeBody) != 0)
+ @compileError("AnalyzeBody must have zero size when crash reports are disabled");
+ return undefined;
+ }
+}
+
+fn dumpStatusReport() !void {
+ const anal = zir_state orelse return;
+ // Note: We have the panic mutex here, so we can safely use the global crash heap.
+ var fba = std.heap.FixedBufferAllocator.init(&crash_heap);
+ const allocator = &fba.allocator;
+
+ const stderr = io.getStdErr().writer();
+ const block: *Scope.Block = anal.block;
+
+ try stderr.writeAll("Analyzing ");
+ try writeFullyQualifiedDeclWithFile(block.src_decl, stderr);
+ try stderr.writeAll("\n");
+
+ print_zir.renderInstructionContext(
+ allocator,
+ anal.body,
+ anal.body_index,
+ block.src_decl.getFileScope(),
+ block.src_decl.src_node,
+ 6, // indent
+ stderr,
+ ) catch |err| switch (err) {
+ error.OutOfMemory => try stderr.writeAll(" \n"),
+ else => |e| return e,
+ };
+ try stderr.writeAll(" For full context, use the command\n zig ast-check -t ");
+ try writeFilePath(block.src_decl.getFileScope(), stderr);
+ try stderr.writeAll("\n\n");
+
+ var parent = anal.parent;
+ while (parent) |curr| {
+ fba.reset();
+ try stderr.writeAll(" in ");
+ try writeFullyQualifiedDeclWithFile(curr.block.src_decl, stderr);
+ try stderr.writeAll("\n > ");
+ print_zir.renderSingleInstruction(
+ allocator,
+ curr.body[curr.body_index],
+ curr.block.src_decl.getFileScope(),
+ curr.block.src_decl.src_node,
+ 6, // indent
+ stderr,
+ ) catch |err| switch (err) {
+ error.OutOfMemory => try stderr.writeAll(" \n"),
+ else => |e| return e,
+ };
+ try stderr.writeAll("\n");
+
+ parent = curr.parent;
+ }
+
+ try stderr.writeAll("\n");
+}
+
+const Scope = Module.Scope;
+const Decl = Module.Decl;
+
+var crash_heap: [16 * 4096]u8 = undefined;
+
+fn writeFilePath(file: *Scope.File, stream: anytype) !void {
+ if (file.pkg.root_src_directory.path) |path| {
+ try stream.writeAll(path);
+ try stream.writeAll(std.fs.path.sep_str);
+ }
+ try stream.writeAll(file.sub_file_path);
+}
+
+fn writeFullyQualifiedDeclWithFile(decl: *Decl, stream: anytype) !void {
+ try writeFilePath(decl.getFileScope(), stream);
+ try stream.writeAll(": ");
+ try decl.namespace.renderFullyQualifiedName(std.mem.sliceTo(decl.name, 0), stream);
+}
+
+fn compilerPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn {
+ PanicSwitch.preDispatch();
+ @setCold(true);
+ const ret_addr = @returnAddress();
+ const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } };
+ PanicSwitch.dispatch(error_return_trace, stack_ctx, msg);
+}
+
+/// Attaches a global SIGSEGV handler
+pub fn attachSegfaultHandler() void {
+ if (!debug.have_segfault_handling_support) {
+ @compileError("segfault handler not supported for this target");
+ }
+ if (builtin.os.tag == .windows) {
+ _ = os.windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
+ return;
+ }
+ var act = os.Sigaction{
+ .handler = .{ .sigaction = handleSegfaultLinux },
+ .mask = os.empty_sigset,
+ .flags = (os.SA.SIGINFO | os.SA.RESTART | os.SA.RESETHAND),
+ };
+
+ os.sigaction(os.SIG.SEGV, &act, null);
+ os.sigaction(os.SIG.ILL, &act, null);
+ os.sigaction(os.SIG.BUS, &act, null);
+}
+
+fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) noreturn {
+ // TODO: use alarm() here to prevent infinite loops
+ PanicSwitch.preDispatch();
+
+ const addr = switch (builtin.os.tag) {
+ .linux => @ptrToInt(info.fields.sigfault.addr),
+ .freebsd => @ptrToInt(info.addr),
+ .netbsd => @ptrToInt(info.info.reason.fault.addr),
+ .openbsd => @ptrToInt(info.data.fault.addr),
+ .solaris => @ptrToInt(info.reason.fault.addr),
+ else => @compileError("TODO implement handleSegfaultLinux for new linux OS"),
+ };
+
+ var err_buffer: [128]u8 = undefined;
+ const error_msg = switch (sig) {
+ os.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault",
+ os.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction",
+ os.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error",
+ else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error",
+ };
+
+ const stack_ctx: StackContext = switch (builtin.cpu.arch) {
+ .i386 => ctx: {
+ const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
+ const ip = @intCast(usize, ctx.mcontext.gregs[os.REG.EIP]);
+ const bp = @intCast(usize, ctx.mcontext.gregs[os.REG.EBP]);
+ break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
+ },
+ .x86_64 => ctx: {
+ const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
+ const ip = switch (builtin.os.tag) {
+ .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]),
+ .freebsd => @intCast(usize, ctx.mcontext.rip),
+ .openbsd => @intCast(usize, ctx.sc_rip),
+ else => unreachable,
+ };
+ const bp = switch (builtin.os.tag) {
+ .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]),
+ .openbsd => @intCast(usize, ctx.sc_rbp),
+ .freebsd => @intCast(usize, ctx.mcontext.rbp),
+ else => unreachable,
+ };
+ break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
+ },
+ .arm => ctx: {
+ const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
+ const ip = @intCast(usize, ctx.mcontext.arm_pc);
+ const bp = @intCast(usize, ctx.mcontext.arm_fp);
+ break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
+ },
+ .aarch64 => ctx: {
+ const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
+ const ip = @intCast(usize, ctx.mcontext.pc);
+ // x29 is the ABI-designated frame pointer
+ const bp = @intCast(usize, ctx.mcontext.regs[29]);
+ break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
+ },
+ else => .not_supported,
+ };
+
+ PanicSwitch.dispatch(null, stack_ctx, error_msg);
+}
+
+const WindowsSegfaultMessage = union(enum) {
+ literal: []const u8,
+ segfault: void,
+ illegal_instruction: void,
+};
+
+fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windows.WINAPI) c_long {
+ switch (info.ExceptionRecord.ExceptionCode) {
+ os.windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }),
+ os.windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault),
+ os.windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction),
+ os.windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }),
+ else => return os.windows.EXCEPTION_CONTINUE_SEARCH,
+ }
+}
+
+fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn {
+ PanicSwitch.preDispatch();
+
+ const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) ctx: {
+ const regs = info.ContextRecord.getRegs();
+ break :ctx StackContext{ .exception = .{ .bp = regs.bp, .ip = regs.ip } };
+ } else ctx: {
+ const addr = @ptrToInt(info.ExceptionRecord.ExceptionAddress);
+ break :ctx StackContext{ .current = .{ .ret_addr = addr } };
+ };
+
+ switch (msg) {
+ .literal => |err| PanicSwitch.dispatch(null, stack_ctx, err),
+ .segfault => {
+ const format_item = "Segmentation fault at address 0x{x}";
+ var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large
+ const to_print = std.fmt.bufPrint(&buf, format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable;
+ PanicSwitch.dispatch(null, stack_ctx, to_print);
+ },
+ .illegal_instruction => {
+ const ip: ?usize = switch (stack_ctx) {
+ .exception => |ex| ex.ip,
+ .current => |cur| cur.ret_addr,
+ .not_supported => null,
+ };
+
+ if (ip) |addr| {
+ const format_item = "Illegal instruction at address 0x{x}";
+ var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large
+ const to_print = std.fmt.bufPrint(&buf, format_item, .{addr}) catch unreachable;
+ PanicSwitch.dispatch(null, stack_ctx, to_print);
+ } else {
+ PanicSwitch.dispatch(null, stack_ctx, "Illegal Instruction");
+ }
+ },
+ }
+}
+
+const StackContext = union(enum) {
+ current: struct {
+ ret_addr: ?usize,
+ },
+ exception: struct {
+ bp: usize,
+ ip: usize,
+ },
+ not_supported: void,
+
+ pub fn dumpStackTrace(ctx: @This()) void {
+ switch (ctx) {
+ .current => |ct| {
+ debug.dumpCurrentStackTrace(ct.ret_addr);
+ },
+ .exception => |ex| {
+ debug.dumpStackTraceFromBase(ex.bp, ex.ip);
+ },
+ .not_supported => {
+ const stderr = io.getStdErr().writer();
+ stderr.writeAll("Stack trace not supported on this platform.\n") catch {};
+ },
+ }
+ }
+};
+
+const PanicSwitch = struct {
+ const RecoverStage = enum {
+ initialize,
+ report_stack,
+ release_mutex,
+ release_ref_count,
+ abort,
+ silent_abort,
+ };
+
+ const RecoverVerbosity = enum {
+ message_and_stack,
+ message_only,
+ silent,
+ };
+
+ const PanicState = struct {
+ recover_stage: RecoverStage = .initialize,
+ recover_verbosity: RecoverVerbosity = .message_and_stack,
+ panic_ctx: StackContext = undefined,
+ panic_trace: ?*const std.builtin.StackTrace = null,
+ awaiting_dispatch: bool = false,
+ };
+
+ /// Counter for the number of threads currently panicking.
+ /// Updated atomically before taking the panic_mutex.
+ /// In recoverable cases, the program will not abort
+ /// until all panicking threads have dumped their traces.
+ var panicking: u8 = 0;
+
+ // Locked to avoid interleaving panic messages from multiple threads.
+ var panic_mutex = std.Thread.Mutex{};
+
+ /// Tracks the state of the current panic. If the code within the
+ /// panic triggers a secondary panic, this allows us to recover.
+ threadlocal var panic_state_raw: PanicState = .{};
+
+ /// The segfault handlers above need to do some work before they can dispatch
+ /// this switch. Calling preDispatch() first makes that work fault tolerant.
+ pub fn preDispatch() void {
+ // TODO: We want segfaults to trigger the panic recursively here,
+ // but if there is a segfault accessing this TLS slot it will cause an
+ // infinite loop. We should use `alarm()` to prevent the infinite
+ // loop and maybe also use a non-thread-local global to detect if
+ // it's happening and print a message.
+ var panic_state: *volatile PanicState = &panic_state_raw;
+ if (panic_state.awaiting_dispatch) {
+ dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack");
+ }
+ panic_state.awaiting_dispatch = true;
+ }
+
+ /// This is the entry point to a panic-tolerant panic handler.
+ /// preDispatch() *MUST* be called exactly once before calling this.
+ /// A threadlocal "recover_stage" is updated throughout the process.
+ /// If a panic happens during the panic, the recover_stage will be
+ /// used to select a recover* function to call to resume the panic.
+ /// The recover_verbosity field is used to handle panics while reporting
+ /// panics within panics. If the panic handler triggers a panic, it will
+ /// attempt to log an additional stack trace for the secondary panic. If
+ /// that panics, it will fall back to just logging the panic message. If
+ /// it can't even do that witout panicing, it will recover without logging
+ /// anything about the internal panic. Depending on the state, "recover"
+ /// here may just mean "call abort".
+ pub fn dispatch(
+ trace: ?*const std.builtin.StackTrace,
+ stack_ctx: StackContext,
+ msg: []const u8,
+ ) noreturn {
+ var panic_state: *volatile PanicState = &panic_state_raw;
+ debug.assert(panic_state.awaiting_dispatch);
+ panic_state.awaiting_dispatch = false;
+ nosuspend switch (panic_state.recover_stage) {
+ .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }),
+ .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }),
+ .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }),
+ .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }),
+ .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }),
+ .silent_abort => goTo(abort, .{}),
+ };
+ }
+
+ noinline fn initPanic(
+ state: *volatile PanicState,
+ trace: ?*const std.builtin.StackTrace,
+ stack: StackContext,
+ msg: []const u8,
+ ) noreturn {
+ // use a temporary so there's only one volatile store
+ const new_state = PanicState{
+ .recover_stage = .abort,
+ .panic_ctx = stack,
+ .panic_trace = trace,
+ };
+ state.* = new_state;
+
+ _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst);
+
+ state.recover_stage = .release_ref_count;
+
+ _ = panic_mutex.acquire();
+
+ state.recover_stage = .release_mutex;
+
+ const stderr = io.getStdErr().writer();
+ if (builtin.single_threaded) {
+ stderr.print("panic: ", .{}) catch goTo(releaseMutex, .{state});
+ } else {
+ const current_thread_id = std.Thread.getCurrentId();
+ stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state});
+ }
+ stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state});
+
+ state.recover_stage = .report_stack;
+
+ dumpStatusReport() catch |err| {
+ stderr.print("\nIntercepted error.{} while dumping current state. Continuing...\n", .{err}) catch {};
+ };
+
+ goTo(reportStack, .{state});
+ }
+
+ noinline fn recoverReportStack(
+ state: *volatile PanicState,
+ trace: ?*const std.builtin.StackTrace,
+ stack: StackContext,
+ msg: []const u8,
+ ) noreturn {
+ recover(state, trace, stack, msg);
+
+ state.recover_stage = .release_mutex;
+ const stderr = io.getStdErr().writer();
+ stderr.writeAll("\nOriginal Error:\n") catch {};
+ goTo(reportStack, .{state});
+ }
+
+ noinline fn reportStack(state: *volatile PanicState) noreturn {
+ state.recover_stage = .release_mutex;
+
+ if (state.panic_trace) |t| {
+ debug.dumpStackTrace(t.*);
+ }
+ state.panic_ctx.dumpStackTrace();
+
+ goTo(releaseMutex, .{state});
+ }
+
+ noinline fn recoverReleaseMutex(
+ state: *volatile PanicState,
+ trace: ?*const std.builtin.StackTrace,
+ stack: StackContext,
+ msg: []const u8,
+ ) noreturn {
+ recover(state, trace, stack, msg);
+ goTo(releaseMutex, .{state});
+ }
+
+ noinline fn releaseMutex(state: *volatile PanicState) noreturn {
+ state.recover_stage = .abort;
+
+ panic_mutex.releaseDirect();
+
+ goTo(releaseRefCount, .{state});
+ }
+
+ noinline fn recoverReleaseRefCount(
+ state: *volatile PanicState,
+ trace: ?*const std.builtin.StackTrace,
+ stack: StackContext,
+ msg: []const u8,
+ ) noreturn {
+ recover(state, trace, stack, msg);
+ goTo(releaseRefCount, .{state});
+ }
+
+ noinline fn releaseRefCount(state: *volatile PanicState) noreturn {
+ state.recover_stage = .abort;
+
+ if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) {
+ // Another thread is panicking, wait for the last one to finish
+ // and call abort()
+
+ // Sleep forever without hammering the CPU
+ var event: std.Thread.StaticResetEvent = .{};
+ event.wait();
+ // This should be unreachable, recurse into recoverAbort.
+ @panic("event.wait() returned");
+ }
+
+ goTo(abort, .{});
+ }
+
+ noinline fn recoverAbort(
+ state: *volatile PanicState,
+ trace: ?*const std.builtin.StackTrace,
+ stack: StackContext,
+ msg: []const u8,
+ ) noreturn {
+ recover(state, trace, stack, msg);
+
+ state.recover_stage = .silent_abort;
+ const stderr = io.getStdErr().writer();
+ stderr.writeAll("Aborting...\n") catch {};
+ goTo(abort, .{});
+ }
+
+ noinline fn abort() noreturn {
+ os.abort();
+ }
+
+ inline fn goTo(comptime func: anytype, args: anytype) noreturn {
+ // TODO: Tailcall is broken right now, but eventually this should be used
+ // to avoid blowing up the stack. It's ok for now though, there are no
+ // cycles in the state machine so the max stack usage is bounded.
+ //@call(.{.modifier = .always_tail}, func, args);
+ @call(.{}, func, args);
+ }
+
+ fn recover(
+ state: *volatile PanicState,
+ trace: ?*const std.builtin.StackTrace,
+ stack: StackContext,
+ msg: []const u8,
+ ) void {
+ switch (state.recover_verbosity) {
+ .message_and_stack => {
+ // lower the verbosity, and restore it at the end if we don't panic.
+ state.recover_verbosity = .message_only;
+
+ const stderr = io.getStdErr().writer();
+ stderr.writeAll("\nPanicked during a panic: ") catch {};
+ stderr.writeAll(msg) catch {};
+ stderr.writeAll("\nInner panic stack:\n") catch {};
+ if (trace) |t| {
+ debug.dumpStackTrace(t.*);
+ }
+ stack.dumpStackTrace();
+
+ state.recover_verbosity = .message_and_stack;
+ },
+ .message_only => {
+ state.recover_verbosity = .silent;
+
+ const stderr = io.getStdErr().writer();
+ stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {};
+ stderr.writeAll(msg) catch {};
+ stderr.writeAll("\n") catch {};
+
+ // If we succeed, restore all the way to dumping the stack.
+ state.recover_verbosity = .message_and_stack;
+ },
+ .silent => {},
+ }
+ }
+};
diff --git a/src/main.zig b/src/main.zig
index 213b9506e7..e0be4b6021 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -20,6 +20,10 @@ const translate_c = @import("translate_c.zig");
const Cache = @import("Cache.zig");
const target_util = @import("target.zig");
const ThreadPool = @import("ThreadPool.zig");
+const crash_report = @import("crash_report.zig");
+
+// Crash report needs to override the panic handler and other root decls
+pub usingnamespace crash_report.root_decls;
pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.log.emerg(format, args);
@@ -134,6 +138,8 @@ var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{
}){};
pub fn main() anyerror!void {
+ crash_report.initialize();
+
var gpa_need_deinit = false;
const gpa = gpa: {
if (!std.builtin.link_libc) {
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 54fd7e632f..1d97f792e6 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -24,15 +24,20 @@ pub fn renderAsTextToFile(
.code = scope_file.zir,
.indent = 0,
.parent_decl_node = 0,
+ .recurse_decls = true,
+ .recurse_blocks = true,
};
+ var raw_stream = std.io.bufferedWriter(fs_file.writer());
+ const stream = raw_stream.writer();
+
const main_struct_inst = Zir.main_struct_inst;
- try fs_file.writer().print("%{d} ", .{main_struct_inst});
- try writer.writeInstToStream(fs_file.writer(), main_struct_inst);
- try fs_file.writeAll("\n");
+ try stream.print("%{d} ", .{main_struct_inst});
+ try writer.writeInstToStream(stream, main_struct_inst);
+ try stream.writeAll("\n");
const imports_index = scope_file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)];
if (imports_index != 0) {
- try fs_file.writeAll("Imports:\n");
+ try stream.writeAll("Imports:\n");
const extra = scope_file.zir.extraData(Zir.Inst.Imports, imports_index);
var import_i: u32 = 0;
@@ -44,13 +49,74 @@ pub fn renderAsTextToFile(
const src: LazySrcLoc = .{ .token_abs = item.data.token };
const import_path = scope_file.zir.nullTerminatedString(item.data.name);
- try fs_file.writer().print(" @import(\"{}\") ", .{
+ try stream.print(" @import(\"{}\") ", .{
std.zig.fmtEscapes(import_path),
});
- try writer.writeSrc(fs_file.writer(), src);
- try fs_file.writer().writeAll("\n");
+ try writer.writeSrc(stream, src);
+ try stream.writeAll("\n");
}
}
+
+ try raw_stream.flush();
+}
+
+pub fn renderInstructionContext(
+ gpa: *Allocator,
+ block: []const Zir.Inst.Index,
+ block_index: usize,
+ scope_file: *Module.Scope.File,
+ parent_decl_node: Ast.Node.Index,
+ indent: u32,
+ stream: anytype,
+) !void {
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
+
+ var writer: Writer = .{
+ .gpa = gpa,
+ .arena = &arena.allocator,
+ .file = scope_file,
+ .code = scope_file.zir,
+ .indent = if (indent < 2) 2 else indent,
+ .parent_decl_node = parent_decl_node,
+ .recurse_decls = false,
+ .recurse_blocks = true,
+ };
+
+ try writer.writeBody(stream, block[0..block_index]);
+ try stream.writeByteNTimes(' ', writer.indent - 2);
+ try stream.print("> %{d} ", .{block[block_index]});
+ try writer.writeInstToStream(stream, block[block_index]);
+ try stream.writeByte('\n');
+ if (block_index + 1 < block.len) {
+ try writer.writeBody(stream, block[block_index + 1 ..]);
+ }
+}
+
+pub fn renderSingleInstruction(
+ gpa: *Allocator,
+ inst: Zir.Inst.Index,
+ scope_file: *Module.Scope.File,
+ parent_decl_node: Ast.Node.Index,
+ indent: u32,
+ stream: anytype,
+) !void {
+ var arena = std.heap.ArenaAllocator.init(gpa);
+ defer arena.deinit();
+
+ var writer: Writer = .{
+ .gpa = gpa,
+ .arena = &arena.allocator,
+ .file = scope_file,
+ .code = scope_file.zir,
+ .indent = indent,
+ .parent_decl_node = parent_decl_node,
+ .recurse_decls = false,
+ .recurse_blocks = false,
+ };
+
+ try stream.print("%{d} ", .{inst});
+ try writer.writeInstToStream(stream, inst);
}
const Writer = struct {
@@ -59,7 +125,9 @@ const Writer = struct {
file: *Module.Scope.File,
code: Zir,
indent: u32,
- parent_decl_node: u32,
+ parent_decl_node: Ast.Node.Index,
+ recurse_decls: bool,
+ recurse_blocks: bool,
fn relativeToNodeIndex(self: *Writer, offset: i32) Ast.Node.Index {
return @bitCast(Ast.Node.Index, offset + @bitCast(i32, self.parent_decl_node));
@@ -567,12 +635,8 @@ const Writer = struct {
try stream.print("\"{}\", ", .{
std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)),
});
- try stream.writeAll("{\n");
- self.indent += 2;
- try self.writeBody(stream, body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}) ");
+ try self.writeBracedBody(stream, body);
+ try stream.writeAll(") ");
try self.writeSrc(stream, inst_data.src());
}
@@ -881,12 +945,8 @@ const Writer = struct {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);
const body = self.code.extra[extra.end..][0..extra.data.body_len];
- try stream.writeAll("{\n");
- self.indent += 2;
- try self.writeBody(stream, body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}) ");
+ try self.writeBracedBody(stream, body);
+ try stream.writeAll(") ");
}
fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
@@ -895,17 +955,11 @@ const Writer = struct {
const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len];
const else_body = self.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
try self.writeInstRef(stream, extra.data.condition);
- try stream.writeAll(", {\n");
- self.indent += 2;
- try self.writeBody(stream, then_body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}, {\n");
- self.indent += 2;
- try self.writeBody(stream, else_body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}) ");
+ try stream.writeAll(", ");
+ try self.writeBracedBody(stream, then_body);
+ try stream.writeAll(", ");
+ try self.writeBracedBody(stream, else_body);
+ try stream.writeAll(") ");
try self.writeSrc(stream, inst_data.src());
}
@@ -963,17 +1017,10 @@ const Writer = struct {
} else {
const prev_parent_decl_node = self.parent_decl_node;
if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
+ try self.writeBracedDecl(stream, body);
+ try stream.writeAll(", {\n");
+
self.indent += 2;
- if (body.len == 0) {
- try stream.writeAll("{}, {\n");
- } else {
- try stream.writeAll("{\n");
- try self.writeBody(stream, body);
-
- try stream.writeByteNTimes(' ', self.indent - 2);
- try stream.writeAll("}, {\n");
- }
-
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
@@ -1096,17 +1143,10 @@ const Writer = struct {
const prev_parent_decl_node = self.parent_decl_node;
if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
+ try self.writeBracedDecl(stream, body);
+ try stream.writeAll(", {\n");
+
self.indent += 2;
- if (body.len == 0) {
- try stream.writeAll("{}, {\n");
- } else {
- try stream.writeAll("{\n");
- try self.writeBody(stream, body);
-
- try stream.writeByteNTimes(' ', self.indent - 2);
- try stream.writeAll("}, {\n");
- }
-
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
@@ -1251,18 +1291,25 @@ const Writer = struct {
try stream.writeAll(")");
}
}
- const tag = self.code.instructions.items(.tag)[decl_index];
- try stream.print(" line({d}) hash({}): %{d} = {s}(", .{
- line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag),
- });
- const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node;
- const sub_decl_node_off = decl_block_inst_data.src_node;
- self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off);
- try self.writePlNodeBlockWithoutSrc(stream, decl_index);
- self.parent_decl_node = parent_decl_node;
- try self.writeSrc(stream, decl_block_inst_data.src());
- try stream.writeAll("\n");
+ if (self.recurse_decls) {
+ const tag = self.code.instructions.items(.tag)[decl_index];
+ try stream.print(" line({d}) hash({}): %{d} = {s}(", .{
+ line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag),
+ });
+
+ const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node;
+ const sub_decl_node_off = decl_block_inst_data.src_node;
+ self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off);
+ try self.writePlNodeBlockWithoutSrc(stream, decl_index);
+ self.parent_decl_node = parent_decl_node;
+ try self.writeSrc(stream, decl_block_inst_data.src());
+ try stream.writeAll("\n");
+ } else {
+ try stream.print(" line({d}) hash({}): %{d} = ...\n", .{
+ line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index,
+ });
+ }
}
return extra_index;
}
@@ -1329,17 +1376,10 @@ const Writer = struct {
} else {
const prev_parent_decl_node = self.parent_decl_node;
if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
+ try self.writeBracedDecl(stream, body);
+ try stream.writeAll(", {\n");
+
self.indent += 2;
- if (body.len == 0) {
- try stream.writeAll("{}, {\n");
- } else {
- try stream.writeAll("{\n");
- try self.writeBody(stream, body);
-
- try stream.writeByteNTimes(' ', self.indent - 2);
- try stream.writeAll("}, {\n");
- }
-
const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;
const body_end = extra_index;
extra_index += bit_bags_count;
@@ -1463,18 +1503,18 @@ const Writer = struct {
try self.writeInstRef(stream, extra.data.operand);
+ self.indent += 2;
+
if (special.body.len != 0) {
const prong_name = switch (special_prong) {
.@"else" => "else",
.under => "_",
else => unreachable,
};
- try stream.print(", {s} => {{\n", .{prong_name});
- self.indent += 2;
- try self.writeBody(stream, special.body);
- self.indent -= 2;
+ try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}");
+ try stream.print("{s} => ", .{prong_name});
+ try self.writeBracedBody(stream, special.body);
}
var extra_index: usize = special.end;
@@ -1488,16 +1528,16 @@ const Writer = struct {
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body_len;
- try stream.writeAll(", ");
- try self.writeInstRef(stream, item_ref);
- try stream.writeAll(" => {\n");
- self.indent += 2;
- try self.writeBody(stream, body);
- self.indent -= 2;
+ try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}");
+ try self.writeInstRef(stream, item_ref);
+ try stream.writeAll(" => ");
+ try self.writeBracedBody(stream, body);
}
}
+
+ self.indent -= 2;
+
try stream.writeAll(") ");
try self.writeSrc(stream, inst_data.src());
}
@@ -1527,18 +1567,18 @@ const Writer = struct {
try self.writeInstRef(stream, extra.data.operand);
+ self.indent += 2;
+
if (special.body.len != 0) {
const prong_name = switch (special_prong) {
.@"else" => "else",
.under => "_",
else => unreachable,
};
- try stream.print(", {s} => {{\n", .{prong_name});
- self.indent += 2;
- try self.writeBody(stream, special.body);
- self.indent -= 2;
+ try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}");
+ try stream.print("{s} => ", .{prong_name});
+ try self.writeBracedBody(stream, special.body);
}
var extra_index: usize = special.end;
@@ -1552,14 +1592,11 @@ const Writer = struct {
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body_len;
- try stream.writeAll(", ");
- try self.writeInstRef(stream, item_ref);
- try stream.writeAll(" => {\n");
- self.indent += 2;
- try self.writeBody(stream, body);
- self.indent -= 2;
+ try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}");
+ try self.writeInstRef(stream, item_ref);
+ try stream.writeAll(" => ");
+ try self.writeBracedBody(stream, body);
}
}
{
@@ -1574,8 +1611,11 @@ const Writer = struct {
const items = self.code.refSlice(extra_index, items_len);
extra_index += items_len;
- for (items) |item_ref| {
- try stream.writeAll(", ");
+ try stream.writeAll(",\n");
+ try stream.writeByteNTimes(' ', self.indent);
+
+ for (items) |item_ref, item_i| {
+ if (item_i != 0) try stream.writeAll(", ");
try self.writeInstRef(stream, item_ref);
}
@@ -1586,7 +1626,9 @@ const Writer = struct {
const item_last = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
extra_index += 1;
- try stream.writeAll(", ");
+ if (range_i != 0 or items.len != 0) {
+ try stream.writeAll(", ");
+ }
try self.writeInstRef(stream, item_first);
try stream.writeAll("...");
try self.writeInstRef(stream, item_last);
@@ -1594,14 +1636,13 @@ const Writer = struct {
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body_len;
- try stream.writeAll(" => {\n");
- self.indent += 2;
- try self.writeBody(stream, body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}");
+ try stream.writeAll(" => ");
+ try self.writeBracedBody(stream, body);
}
}
+
+ self.indent -= 2;
+
try stream.writeAll(") ");
try self.writeSrc(stream, inst_data.src());
}
@@ -1796,12 +1837,8 @@ const Writer = struct {
const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index);
const body = self.code.extra[extra.end..][0..extra.data.body_len];
try self.writeInstRef(stream, inst_data.lhs);
- try stream.writeAll(", {\n");
- self.indent += 2;
- try self.writeBody(stream, body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("})");
+ try stream.writeAll(", ");
+ try self.writeBracedBody(stream, body);
}
fn writeIntType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
@@ -1846,12 +1883,8 @@ const Writer = struct {
if (ret_ty_body.len == 0) {
try stream.writeAll("ret_ty=void");
} else {
- try stream.writeAll("ret_ty={\n");
- self.indent += 2;
- try self.writeBody(stream, ret_ty_body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}");
+ try stream.writeAll("ret_ty=");
+ try self.writeBracedBody(stream, ret_ty_body);
}
try self.writeOptionalInstRef(stream, ", cc=", cc);
@@ -1860,16 +1893,9 @@ const Writer = struct {
try self.writeFlag(stream, ", extern", is_extern);
try self.writeFlag(stream, ", inferror", inferred_error_set);
- if (body.len == 0) {
- try stream.writeAll(", body={}) ");
- } else {
- try stream.writeAll(", body={\n");
- self.indent += 2;
- try self.writeBody(stream, body);
- self.indent -= 2;
- try stream.writeByteNTimes(' ', self.indent);
- try stream.writeAll("}) ");
- }
+ try stream.writeAll(", body=");
+ try self.writeBracedBody(stream, body);
+ try stream.writeAll(") ");
if (body.len != 0) {
try stream.print("(lbrace={d}:{d},rbrace={d}:{d}) ", .{
src_locs.lbrace_line, @truncate(u16, src_locs.columns),
@@ -1929,18 +1955,19 @@ const Writer = struct {
}
fn writeSrc(self: *Writer, stream: anytype, src: LazySrcLoc) !void {
- const tree = self.file.tree;
- const src_loc: Module.SrcLoc = .{
- .file_scope = self.file,
- .parent_decl_node = self.parent_decl_node,
- .lazy = src,
- };
- // Caller must ensure AST tree is loaded.
- const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable;
- const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off);
- try stream.print("{s}:{d}:{d}", .{
- @tagName(src), delta_line.line + 1, delta_line.column + 1,
- });
+ if (self.file.tree_loaded) {
+ const tree = self.file.tree;
+ const src_loc: Module.SrcLoc = .{
+ .file_scope = self.file,
+ .parent_decl_node = self.parent_decl_node,
+ .lazy = src,
+ };
+ const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable;
+ const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off);
+ try stream.print("{s}:{d}:{d}", .{
+ @tagName(src), delta_line.line + 1, delta_line.column + 1,
+ });
+ }
}
fn writeSrcNode(self: *Writer, stream: anytype, src_node: ?i32) !void {
@@ -1950,6 +1977,43 @@ const Writer = struct {
return self.writeSrc(stream, src);
}
+ fn writeBracedDecl(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void {
+ try self.writeBracedBodyConditional(stream, body, self.recurse_decls);
+ }
+
+ fn writeBracedBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void {
+ try self.writeBracedBodyConditional(stream, body, self.recurse_blocks);
+ }
+
+ fn writeBracedBodyConditional(self: *Writer, stream: anytype, body: []const Zir.Inst.Index, enabled: bool) !void {
+ if (body.len == 0) {
+ try stream.writeAll("{}");
+ } else if (enabled) {
+ try stream.writeAll("{\n");
+ self.indent += 2;
+ try self.writeBody(stream, body);
+ self.indent -= 2;
+ try stream.writeByteNTimes(' ', self.indent);
+ try stream.writeAll("}");
+ } else if (body.len == 1) {
+ try stream.writeByte('{');
+ try self.writeInstIndex(stream, body[0]);
+ try stream.writeByte('}');
+ } else if (body.len == 2) {
+ try stream.writeByte('{');
+ try self.writeInstIndex(stream, body[0]);
+ try stream.writeAll(", ");
+ try self.writeInstIndex(stream, body[1]);
+ try stream.writeByte('}');
+ } else {
+ try stream.writeByte('{');
+ try self.writeInstIndex(stream, body[0]);
+ try stream.writeAll("..");
+ try self.writeInstIndex(stream, body[body.len - 1]);
+ try stream.writeByte('}');
+ }
+ }
+
fn writeBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void {
for (body) |inst| {
try stream.writeByteNTimes(' ', self.indent);
From c82c3585c8fdf02820747c118c78957e2eb5d072 Mon Sep 17 00:00:00 2001
From: Martin Wickham
Date: Thu, 30 Sep 2021 00:19:21 -0500
Subject: [PATCH 158/160] Add error message to test runner for bad arguments
---
lib/std/special/test_runner.zig | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig
index b762e7784e..e72204377f 100644
--- a/lib/std/special/test_runner.zig
+++ b/lib/std/special/test_runner.zig
@@ -13,6 +13,12 @@ fn processArgs() void {
const args = std.process.argsAlloc(&args_allocator.allocator) catch {
@panic("Too many bytes passed over the CLI to the test runner");
};
+ if (args.len != 2) {
+ const self_name = if (args.len >= 1) args[0] else if (builtin.os.tag == .windows) "test.exe" else "test";
+ const zig_ext = if (builtin.os.tag == .windows) ".exe" else "";
+ std.debug.print("Usage: {s} path/to/zig{s}\n", .{ self_name, zig_ext });
+ @panic("Wrong number of command line arguments");
+ }
std.testing.zig_exe_path = args[1];
}
From 5e7406bdd9f942900dceb2f917ed5f64b6f2ba00 Mon Sep 17 00:00:00 2001
From: g-w1 <58830309+g-w1@users.noreply.github.com>
Date: Thu, 30 Sep 2021 18:31:27 -0400
Subject: [PATCH 159/160] stage2: implement array_init instruction (#9843)
* stage2: array mul support more types of operands
* stage2: array cat support more types of operands
* print_zir: print array_init
* stage2: implement Sema for array_init
---
src/Sema.zig | 139 ++++++++++++++++++++++++++--------------
src/print_zir.zig | 19 +++++-
test/behavior/array.zig | 6 ++
3 files changed, 114 insertions(+), 50 deletions(-)
diff --git a/src/Sema.zig b/src/Sema.zig
index f9f0160064..7c2ef32ad3 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -6111,35 +6111,36 @@ fn zirArrayCat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| {
const final_len = lhs_info.len + rhs_info.len;
- if (lhs_ty.zigTypeTag() == .Pointer) {
- var anon_decl = try block.startAnonDecl();
- defer anon_decl.deinit();
+ const is_pointer = lhs_ty.zigTypeTag() == .Pointer;
+ var anon_decl = try block.startAnonDecl();
+ defer anon_decl.deinit();
- const lhs_sub_val = (try lhs_val.pointerDeref(anon_decl.arena())).?;
- const rhs_sub_val = (try rhs_val.pointerDeref(anon_decl.arena())).?;
- const buf = try anon_decl.arena().alloc(Value, final_len);
- {
- var i: u64 = 0;
- while (i < lhs_info.len) : (i += 1) {
- const val = try lhs_sub_val.elemValue(sema.arena, i);
- buf[i] = try val.copy(anon_decl.arena());
- }
+ const lhs_sub_val = if (is_pointer) (try lhs_val.pointerDeref(anon_decl.arena())).? else lhs_val;
+ const rhs_sub_val = if (is_pointer) (try rhs_val.pointerDeref(anon_decl.arena())).? else rhs_val;
+ const buf = try anon_decl.arena().alloc(Value, final_len);
+ {
+ var i: u64 = 0;
+ while (i < lhs_info.len) : (i += 1) {
+ const val = try lhs_sub_val.elemValue(sema.arena, i);
+ buf[i] = try val.copy(anon_decl.arena());
}
- {
- var i: u64 = 0;
- while (i < rhs_info.len) : (i += 1) {
- const val = try rhs_sub_val.elemValue(sema.arena, i);
- buf[lhs_info.len + i] = try val.copy(anon_decl.arena());
- }
- }
- const ty = if (res_sent) |rs|
- try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type, .sentinel = rs })
- else
- try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type });
- const val = try Value.Tag.array.create(anon_decl.arena(), buf);
- return sema.analyzeDeclRef(try anon_decl.finish(ty, val));
}
- return sema.mod.fail(&block.base, lhs_src, "TODO array_cat more types of Values", .{});
+ {
+ var i: u64 = 0;
+ while (i < rhs_info.len) : (i += 1) {
+ const val = try rhs_sub_val.elemValue(sema.arena, i);
+ buf[lhs_info.len + i] = try val.copy(anon_decl.arena());
+ }
+ }
+ const ty = if (res_sent) |rs|
+ try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type, .sentinel = rs })
+ else
+ try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type });
+ const val = try Value.Tag.array.create(anon_decl.arena(), buf);
+ return if (is_pointer)
+ sema.analyzeDeclRef(try anon_decl.finish(ty, val))
+ else
+ sema.analyzeDeclVal(block, .unneeded, try anon_decl.finish(ty, val));
} else {
return sema.mod.fail(&block.base, lhs_src, "TODO runtime array_cat", .{});
}
@@ -6179,29 +6180,30 @@ fn zirArrayMul(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr
const final_len = std.math.mul(u64, mulinfo.len, tomulby) catch return sema.mod.fail(&block.base, rhs_src, "operation results in overflow", .{});
if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
- if (lhs_ty.zigTypeTag() == .Pointer) {
- var anon_decl = try block.startAnonDecl();
- defer anon_decl.deinit();
- const lhs_sub_val = (try lhs_val.pointerDeref(anon_decl.arena())).?;
+ var anon_decl = try block.startAnonDecl();
+ defer anon_decl.deinit();
+ const lhs_sub_val = if (lhs_ty.zigTypeTag() == .Pointer) (try lhs_val.pointerDeref(anon_decl.arena())).? else lhs_val;
+ const final_ty = if (mulinfo.sentinel) |sent|
+ try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type, .sentinel = sent })
+ else
+ try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type });
+ const buf = try anon_decl.arena().alloc(Value, final_len);
- const final_ty = if (mulinfo.sentinel) |sent|
- try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type, .sentinel = sent })
- else
- try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type });
-
- const buf = try anon_decl.arena().alloc(Value, final_len);
- var i: u64 = 0;
- while (i < tomulby) : (i += 1) {
- var j: u64 = 0;
- while (j < mulinfo.len) : (j += 1) {
- const val = try lhs_sub_val.elemValue(sema.arena, j);
- buf[mulinfo.len * i + j] = try val.copy(anon_decl.arena());
- }
+ // the actual loop
+ var i: u64 = 0;
+ while (i < tomulby) : (i += 1) {
+ var j: u64 = 0;
+ while (j < mulinfo.len) : (j += 1) {
+ const val = try lhs_sub_val.elemValue(sema.arena, j);
+ buf[mulinfo.len * i + j] = try val.copy(anon_decl.arena());
}
- const val = try Value.Tag.array.create(anon_decl.arena(), buf);
- return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val));
}
- return sema.mod.fail(&block.base, lhs_src, "TODO array_mul more types of Values", .{});
+ const val = try Value.Tag.array.create(anon_decl.arena(), buf);
+ if (lhs_ty.zigTypeTag() == .Pointer) {
+ return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val));
+ } else {
+ return sema.analyzeDeclVal(block, .unneeded, try anon_decl.finish(final_ty, val));
+ }
}
return sema.mod.fail(&block.base, lhs_src, "TODO runtime array_mul", .{});
}
@@ -8227,8 +8229,49 @@ fn zirArrayInit(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref:
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
- _ = is_ref;
- return sema.mod.fail(&block.base, src, "TODO: Sema.zirArrayInit", .{});
+ const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
+ const args = sema.code.refSlice(extra.end, extra.data.operands_len);
+
+ var resolved_args = try sema.mod.gpa.alloc(Air.Inst.Ref, args.len);
+ for (args) |arg, i| resolved_args[i] = sema.resolveInst(arg);
+
+ var all_args_comptime = for (resolved_args) |arg| {
+ if ((try sema.resolveMaybeUndefVal(block, src, arg)) == null) break false;
+ } else true;
+
+ if (all_args_comptime) {
+ var anon_decl = try block.startAnonDecl();
+ defer anon_decl.deinit();
+ assert(!(resolved_args.len == 0));
+ const final_ty = try Type.Tag.array.create(anon_decl.arena(), .{ .len = resolved_args.len, .elem_type = sema.typeOf(resolved_args[0]) });
+ const buf = try anon_decl.arena().alloc(Value, resolved_args.len);
+ for (resolved_args) |arg, i| {
+ buf[i] = (try sema.resolveMaybeUndefVal(block, src, arg)).?;
+ }
+
+ const val = try Value.Tag.array.create(anon_decl.arena(), buf);
+ if (is_ref)
+ return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val))
+ else
+ return sema.analyzeDeclVal(block, .unneeded, try anon_decl.finish(final_ty, val));
+ }
+
+ assert(!(resolved_args.len == 0));
+ const array_ty = try Type.Tag.array.create(sema.arena, .{ .len = resolved_args.len, .elem_type = sema.typeOf(resolved_args[0]) });
+ const final_ty = try Type.ptr(sema.arena, .{
+ .pointee_type = array_ty,
+ .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local),
+ });
+ const alloc = try block.addTy(.alloc, final_ty);
+
+ for (resolved_args) |arg, i| {
+ const pointer_to_array_at_index = try block.addBinOp(.ptr_elem_ptr, alloc, try sema.addIntUnsigned(Type.initTag(.u64), i));
+ _ = try block.addBinOp(.store, pointer_to_array_at_index, arg);
+ }
+ return if (is_ref)
+ alloc
+ else
+ try sema.analyzeLoad(block, .unneeded, alloc, .unneeded);
}
fn zirArrayInitAnon(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref {
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 1d97f792e6..c53c92f6bf 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -259,15 +259,16 @@ const Writer = struct {
.@"break",
.break_inline,
=> try self.writeBreak(stream, inst),
+ .array_init,
+ .array_init_ref,
+ => try self.writeArrayInit(stream, inst),
.elem_ptr_node,
.elem_val_node,
.slice_start,
.slice_end,
.slice_sentinel,
- .array_init,
.array_init_anon,
- .array_init_ref,
.array_init_anon_ref,
.union_init_ptr,
.shuffle,
@@ -1860,6 +1861,20 @@ const Writer = struct {
try stream.writeAll(")");
}
+ fn writeArrayInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+
+ const extra = self.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
+ const args = self.code.refSlice(extra.end, extra.data.operands_len);
+
+ try stream.writeAll(".{");
+ for (args) |arg, i| {
+ if (i != 0) try stream.writeAll(", ");
+ try self.writeInstRef(stream, arg);
+ }
+ try stream.writeAll("})");
+ }
+
fn writeUnreachable(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].@"unreachable";
const safety_str = if (inst_data.safety) "safe" else "unsafe";
diff --git a/test/behavior/array.zig b/test/behavior/array.zig
index 8cf0042d5f..8250cdea06 100644
--- a/test/behavior/array.zig
+++ b/test/behavior/array.zig
@@ -27,3 +27,9 @@ test "arrays" {
fn getArrayLen(a: []const u32) usize {
return a.len;
}
+
+test "array init with mult" {
+ const a = 'a';
+ var i: [8]u8 = [2]u8{ a, 'b' } ** 4;
+ try expect(std.mem.eql(u8, &i, "abababab"));
+}
From c4cd592f0e1eeff5a4056796610d97010ae4e38c Mon Sep 17 00:00:00 2001
From: Nathan Michaels
Date: Thu, 30 Sep 2021 00:19:23 -0400
Subject: [PATCH 160/160] Fix a typo in @ceil documentation.
---
doc/langref.html.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 2e69e37097..ec9d96d069 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -8866,7 +8866,7 @@ fn doTheTest() !void {
{#header_open|@ceil#}
{#syntax#}@ceil(value: anytype) @TypeOf(value){#endsyntax#}
- Returns the largest integral value not less than the given floating point number.
+ Returns the smallest integral value not less than the given floating point number.
Uses a dedicated hardware instruction when available.
| |