From 19691c0b174f283ffe5b6c3fe8533ef458736064 Mon Sep 17 00:00:00 2001
From: Andrew Kelley
{#syntax#}@atomicRmw(comptime T: type, ptr: *T, comptime op: builtin.AtomicRmwOp, operand: T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}
@@ -7242,7 +7244,9 @@ fn func(y: *i32) void {
{#syntax#}@atomicStore(comptime T: type, ptr: *T, value: T, comptime ordering: builtin.AtomicOrder) void{#endsyntax#}
@@ -7252,6 +7256,7 @@ fn func(y: *i32) void { {#syntax#}T{#endsyntax#} must be a pointer, a {#syntax#}bool{#endsyntax#}, a float, an integer or an enum.
+ {#see_also|@atomicLoad|@atomicRmw|@fence|@cmpxchgWeak|@cmpxchgStrong#} {#header_close#} {#header_open|@bitCast#} @@ -7540,8 +7545,9 @@ fn cmpxchgStrongButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_v an integer or an enum.{#syntax#}@typeInfo(@TypeOf(ptr)).Pointer.alignment{#endsyntax#} must be {#syntax#}>= @sizeOf(T).{#endsyntax#}
- {#see_also|Compile Variables|cmpxchgWeak#} + {#see_also|@atomicStore|@atomicLoad|@atomicRmw|@fence|@cmpxchgWeak#} {#header_close#} + {#header_open|@cmpxchgWeak#}{#syntax#}@cmpxchgWeak(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T{#endsyntax#}
@@ -7569,7 +7575,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val an integer or an enum.
{#syntax#}@typeInfo(@TypeOf(ptr)).Pointer.alignment{#endsyntax#} must be {#syntax#}>= @sizeOf(T).{#endsyntax#}
- {#see_also|Compile Variables|cmpxchgStrong#} + {#see_also|@atomicStore|@atomicLoad|@atomicRmw|@fence|@cmpxchgStrong#} {#header_close#} {#header_open|@compileError#} @@ -7849,7 +7855,7 @@ export fn @"A function name that is a complete sentence."() void {}{#syntax#}AtomicOrder{#endsyntax#} can be found with {#syntax#}@import("std").builtin.AtomicOrder{#endsyntax#}.
- {#see_also|Compile Variables#} + {#see_also|@atomicStore|@atomicLoad|@atomicRmw|@cmpxchgWeak|@cmpxchgStrong#} {#header_close#} {#header_open|@field#} diff --git a/src/Air.zig b/src/Air.zig index e4289c2826..2834699d69 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -127,14 +127,11 @@ pub const Inst = struct { /// Lowers to a hardware trap instruction, or the next best thing. /// Result type is always void. breakpoint, - /// Lowers to a memory fence instruction. - /// Result type is always void. - /// Uses the `fence` field. - fence, /// Function call. /// 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, + /// `<`. Result type is always bool. /// Uses the `bin_op` field. cmp_lt, @@ -153,6 +150,7 @@ pub const Inst = struct { /// `!=`. Result type is always bool. /// Uses the `bin_op` field. cmp_neq, + /// Conditional branch. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `pl_op` field. Operand is the condition. Payload is `CondBr`. @@ -313,10 +311,33 @@ pub const Inst = struct { /// Given a pointer to an array, return a slice. /// Uses the `ty_op` field. array_to_slice, + /// Uses the `ty_pl` field with payload `Cmpxchg`. cmpxchg_weak, /// Uses the `ty_pl` field with payload `Cmpxchg`. cmpxchg_strong, + /// Lowers to a memory fence instruction. + /// Result type is always void. + /// Uses the `fence` field. + fence, + /// Atomically load from a pointer. + /// Result type is the element type of the pointer. + /// Uses the `atomic_load` field. + atomic_load, + /// Atomically store through a pointer. + /// Result type is always `void`. + /// Uses the `bin_op` field. LHS is pointer, RHS is element. + atomic_store_unordered, + /// Same as `atomic_store_unordered` but with `AtomicOrder.Monotonic`. + atomic_store_monotonic, + /// Same as `atomic_store_unordered` but with `AtomicOrder.Release`. + atomic_store_release, + /// Same as `atomic_store_unordered` but with `AtomicOrder.SeqCst`. + atomic_store_seq_cst, + /// Atomically read-modify-write via a pointer. + /// Result type is the element type of the pointer. + /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`. + atomic_rmw, pub fn fromCmpOp(op: std.math.CompareOperator) Tag { return switch (op) { @@ -385,6 +406,10 @@ pub const Inst = struct { column: u32, }, fence: std.builtin.AtomicOrder, + atomic_load: struct { + ptr: Ref, + order: std.builtin.AtomicOrder, + }, // Make sure we don't accidentally add a field to make this union // bigger than expected. Note that in Debug builds, Zig is allowed @@ -469,6 +494,21 @@ pub const Cmpxchg = struct { } }; +pub const AtomicRmw = struct { + operand: Inst.Ref, + /// 0b00000000000000000000000000000XXX - ordering + /// 0b0000000000000000000000000XXXX000 - op + flags: u32, + + pub fn ordering(self: AtomicRmw) std.builtin.AtomicOrder { + return @intToEnum(std.builtin.AtomicOrder, @truncate(u3, self.flags)); + } + + pub fn op(self: AtomicRmw) std.builtin.AtomicRmwOp { + return @intToEnum(std.builtin.AtomicRmwOp, @truncate(u4, self.flags >> 3)); + } +}; + pub fn getMainBody(air: Air) []const Air.Inst.Index { const body_index = air.extra[@enumToInt(ExtraIndex.main_block)]; const extra = air.extraData(Block, body_index); @@ -572,6 +612,10 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .dbg_stmt, .store, .fence, + .atomic_store_unordered, + .atomic_store_monotonic, + .atomic_store_release, + .atomic_store_seq_cst, => return Type.initTag(.void), .ptrtoint, @@ -594,6 +638,14 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { const inner_ptr_ty = outer_ptr_ty.elemType(); return inner_ptr_ty.elemType(); }, + .atomic_load => { + const ptr_ty = air.typeOf(datas[inst].atomic_load.ptr); + return ptr_ty.elemType(); + }, + .atomic_rmw => { + const ptr_ty = air.typeOf(datas[inst].pl_op.operand); + return ptr_ty.elemType(); + }, } } diff --git a/src/AstGen.zig b/src/AstGen.zig index ac4c807027..b176136ba4 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7316,6 +7316,7 @@ fn builtinCall( .atomic_load => { const int_type = try typeExpr(gz, scope, params[0]); + // TODO allow this pointer type to be volatile const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ .ptr_type_simple = .{ .is_allowzero = false, @@ -7325,16 +7326,17 @@ fn builtinCall( .elem_type = int_type, }, } }); - const ptr = try expr(gz, scope, .{ .ty = ptr_type }, params[1]); - const ordering = try expr(gz, scope, .{ .ty = .atomic_order_type }, params[2]); const result = try gz.addPlNode(.atomic_load, node, Zir.Inst.Bin{ - .lhs = ptr, - .rhs = ordering, + // zig fmt: off + .lhs = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]), + .rhs = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[2]), + // zig fmt: on }); return rvalue(gz, rl, result, node); }, .atomic_rmw => { const int_type = try typeExpr(gz, scope, params[0]); + // TODO allow this pointer type to be volatile const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ .ptr_type_simple = .{ .is_allowzero = false, @@ -7344,20 +7346,19 @@ fn builtinCall( .elem_type = int_type, }, } }); - const ptr = try expr(gz, scope, .{ .ty = ptr_type }, params[1]); - const operation = try expr(gz, scope, .{ .ty = .atomic_rmw_op_type }, params[2]); - const operand = try expr(gz, scope, .{ .ty = int_type }, params[3]); - const ordering = try expr(gz, scope, .{ .ty = .atomic_order_type }, params[4]); const result = try gz.addPlNode(.atomic_rmw, node, Zir.Inst.AtomicRmw{ - .ptr = ptr, - .operation = operation, - .operand = operand, - .ordering = ordering, + // zig fmt: off + .ptr = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]), + .operation = try expr(gz, scope, .{ .coerced_ty = .atomic_rmw_op_type }, params[2]), + .operand = try expr(gz, scope, .{ .coerced_ty = int_type }, params[3]), + .ordering = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[4]), + // zig fmt: on }); return rvalue(gz, rl, result, node); }, .atomic_store => { const int_type = try typeExpr(gz, scope, params[0]); + // TODO allow this pointer type to be volatile const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ .ptr_type_simple = .{ .is_allowzero = false, @@ -7367,13 +7368,12 @@ fn builtinCall( .elem_type = int_type, }, } }); - const ptr = try expr(gz, scope, .{ .ty = ptr_type }, params[1]); - const operand = try expr(gz, scope, .{ .ty = int_type }, params[2]); - const ordering = try expr(gz, scope, .{ .ty = .atomic_order_type }, params[3]); const result = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{ - .ptr = ptr, - .operand = operand, - .ordering = ordering, + // zig fmt: off + .ptr = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]), + .operand = try expr(gz, scope, .{ .coerced_ty = int_type }, params[2]), + .ordering = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[3]), + // zig fmt: on }); return rvalue(gz, rl, result, node); }, @@ -7456,12 +7456,11 @@ fn builtinCall( }, .Vector => { const result = try gz.addPlNode(.vector_type, node, Zir.Inst.Bin{ - .lhs = try comptimeExpr(gz, scope, .{.ty = .u32_type}, params[0]), + .lhs = try comptimeExpr(gz, scope, .{ .ty = .u32_type }, params[0]), .rhs = try typeExpr(gz, scope, params[1]), }); return rvalue(gz, rl, result, node); }, - } // zig fmt: on } diff --git a/src/Liveness.zig b/src/Liveness.zig index a7519a33ee..599507500e 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -252,6 +252,10 @@ fn analyzeInst( .ptr_ptr_elem_val, .shl, .shr, + .atomic_store_unordered, + .atomic_store_monotonic, + .atomic_store_release, + .atomic_store_seq_cst, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); @@ -345,6 +349,15 @@ fn analyzeInst( const extra = a.air.extraData(Air.Cmpxchg, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.ptr, extra.expected_value, extra.new_value }); }, + .atomic_load => { + const ptr = inst_datas[inst].atomic_load.ptr; + return trackOperands(a, new_set, inst, main_tomb, .{ ptr, .none, .none }); + }, + .atomic_rmw => { + const pl_op = inst_datas[inst].pl_op; + 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 }); + }, .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 e679f03fcc..c163178890 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7549,6 +7549,19 @@ fn resolveAtomicOrder( return val.toEnum(std.builtin.AtomicOrder); } +fn resolveAtomicRmwOp( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, +) CompileError!std.builtin.AtomicRmwOp { + const atomic_rmw_op_ty = try sema.getBuiltinType(block, src, "AtomicRmwOp"); + const air_ref = sema.resolveInst(zir_ref); + const coerced = try sema.coerce(block, atomic_rmw_op_ty, air_ref, src); + const val = try sema.resolveConstValue(block, src, coerced); + return val.toEnum(std.builtin.AtomicRmwOp); +} + fn zirCmpxchg( sema: *Sema, block: *Scope.Block, @@ -7664,14 +7677,108 @@ fn zirSelect(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro fn zirAtomicLoad(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.zirAtomicLoad", .{}); + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + // zig fmt: off + const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + // zig fmt: on + const ptr = sema.resolveInst(extra.lhs); + const elem_ty = sema.typeOf(ptr).elemType(); + try sema.checkAtomicOperandType(block, elem_ty_src, elem_ty); + const order = try sema.resolveAtomicOrder(block, order_src, extra.rhs); + + switch (order) { + .Release, .AcqRel => { + return sema.mod.fail( + &block.base, + order_src, + "@atomicLoad atomic ordering must not be Release or AcqRel", + .{}, + ); + }, + else => {}, + } + + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { + if (try ptr_val.pointerDeref(sema.arena)) |elem_val| { + return sema.addConstant(elem_ty, elem_val); + } + } + + try sema.requireRuntimeBlock(block, ptr_src); + return block.addInst(.{ + .tag = .atomic_load, + .data = .{ .atomic_load = .{ + .ptr = ptr, + .order = order, + } }, + }); } fn zirAtomicRmw(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.AtomicRmw, inst_data.payload_index).data; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirAtomicRmw", .{}); + // zig fmt: off + const operand_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const op_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const operand_src : LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; + const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg4 = inst_data.src_node }; + // zig fmt: on + const ptr = sema.resolveInst(extra.ptr); + const operand_ty = sema.typeOf(ptr).elemType(); + try sema.checkAtomicOperandType(block, operand_ty_src, operand_ty); + const op = try sema.resolveAtomicRmwOp(block, op_src, extra.operation); + + switch (operand_ty.zigTypeTag()) { + .Enum => if (op != .Xchg) { + return mod.fail(&block.base, op_src, "@atomicRmw with enum only allowed with .Xchg", .{}); + }, + .Bool => if (op != .Xchg) { + return mod.fail(&block.base, op_src, "@atomicRmw with bool only allowed with .Xchg", .{}); + }, + .Float => switch (op) { + .Xchg, .Add, .Sub => {}, + else => return mod.fail(&block.base, op_src, "@atomicRmw with float only allowed with .Xchg, .Add, and .Sub", .{}), + }, + else => {}, + } + const operand = try sema.coerce(block, operand_ty, sema.resolveInst(extra.operand), operand_src); + const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering); + + if (order == .Unordered) { + return mod.fail(&block.base, order_src, "@atomicRmw atomic ordering must not be Unordered", .{}); + } + + // special case zero bit types + if (try sema.typeHasOnePossibleValue(block, operand_ty_src, operand_ty)) |val| { + return sema.addConstant(operand_ty, val); + } + + const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |operand_val| { + _ = ptr_val; + _ = operand_val; + return mod.fail(&block.base, src, "TODO implement Sema for @atomicRmw at comptime", .{}); + } else break :rs operand_src; + } else ptr_src; + + const flags: u32 = @as(u32, @enumToInt(order)) | (@as(u32, @enumToInt(op)) << 3); + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addInst(.{ + .tag = .atomic_rmw, + .data = .{ .pl_op = .{ + .operand = ptr, + .payload = try sema.addExtra(Air.AtomicRmw{ + .operand = operand, + .flags = flags, + }), + } }, + }); } fn zirAtomicStore(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8848,7 +8955,7 @@ fn coerce( if (dest_type.eql(inst_ty)) return inst; - const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty); + const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false); if (in_memory_result == .ok) { return sema.bitcast(block, dest_type, inst, inst_src); } @@ -8890,11 +8997,12 @@ fn coerce( const array_type = inst_ty.elemType(); if (array_type.zigTypeTag() != .Array) break :src_array_ptr; const array_elem_type = array_type.elemType(); - if (inst_ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr; + const dest_is_mut = !dest_type.isConstPtr(); + if (inst_ty.isConstPtr() and dest_is_mut) break :src_array_ptr; if (inst_ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr; const dst_elem_type = dest_type.elemType(); - switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) { + switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut)) { .ok => {}, .no_match => break :src_array_ptr, } @@ -9001,10 +9109,80 @@ const InMemoryCoercionResult = enum { no_match, }; -fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult { +/// If pointers have the same representation in runtime memory, a bitcast AIR instruction +/// may be used for the coercion. +/// * `const` attribute can be gained +/// * `volatile` attribute can be gained +/// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if !dest_is_mut +/// * alignment can be decreased +/// * bit offset attributes must match exactly +/// * `*`/`[*]` must match exactly, but `[*c]` matches either one +/// * 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 { if (dest_type.eql(src_type)) return .ok; + if (dest_type.zigTypeTag() == .Pointer and + src_type.zigTypeTag() == .Pointer) + { + 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); + if (child == .no_match) { + return child; + } + + const ok_sent = dest_info.sentinel == null or src_info.size == .C or + (src_info.sentinel != null and + dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type)); + if (!ok_sent) { + return .no_match; + } + + const ok_ptr_size = src_info.size == dest_info.size or + src_info.size == .C or dest_info.size == .C; + if (!ok_ptr_size) { + return .no_match; + } + + const ok_cv_qualifiers = + (src_info.mutable or !dest_info.mutable) and + (!src_info.@"volatile" or dest_info.@"volatile"); + + if (!ok_cv_qualifiers) { + return .no_match; + } + + const ok_allows_zero = (dest_info.@"allowzero" and + (src_info.@"allowzero" or !dest_is_mut)) or + (!dest_info.@"allowzero" and !src_info.@"allowzero"); + if (!ok_allows_zero) { + return .no_match; + } + + if (dest_type.hasCodeGenBits() != src_type.hasCodeGenBits()) { + return .no_match; + } + + if (src_info.host_size != dest_info.host_size or + src_info.bit_offset != dest_info.bit_offset) + { + return .no_match; + } + + assert(src_info.@"align" != 0); + assert(dest_info.@"align" != 0); + + if (dest_info.@"align" > src_info.@"align") { + return .no_match; + } + + return .ok; + } + // TODO: implement more of this function return .no_match; diff --git a/src/codegen.zig b/src/codegen.zig index 75e7a56b15..08ee358bff 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -860,6 +860,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .array_to_slice => try self.airArrayToSlice(inst), .cmpxchg_strong => try self.airCmpxchg(inst), .cmpxchg_weak => try self.airCmpxchg(inst), + .atomic_rmw => try self.airAtomicRmw(inst), + .atomic_load => try self.airAtomicLoad(inst), + + .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), + .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), + .atomic_store_release => try self.airAtomicStore(inst, .Release), + .atomic_store_seq_cst => try self.airAtomicStore(inst, .SeqCst), .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0), .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1), @@ -4773,6 +4780,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ extra.ptr, extra.expected_value, extra.new_value }); } + fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch}); + } + + fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airAtomicLoad for {}", .{self.target.cpu.arch}); + } + + fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void { + _ = inst; + _ = order; + return self.fail("TODO implement airAtomicStore 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 a2e2d7b20d..5eb4388a9e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -914,6 +914,13 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .array_to_slice => try airArrayToSlice(o, inst), .cmpxchg_weak => try airCmpxchg(o, inst, "weak"), .cmpxchg_strong => try airCmpxchg(o, inst, "strong"), + .atomic_rmw => try airAtomicRmw(o, inst), + .atomic_load => try airAtomicLoad(o, inst), + + .atomic_store_unordered => try airAtomicStore(o, inst, toMemoryOrder(.Unordered)), + .atomic_store_monotonic => try airAtomicStore(o, inst, toMemoryOrder(.Monotonic)), + .atomic_store_release => try airAtomicStore(o, inst, toMemoryOrder(.Release)), + .atomic_store_seq_cst => try airAtomicStore(o, inst, toMemoryOrder(.SeqCst)), .struct_field_ptr_index_0 => try airStructFieldPtrIndex(o, inst, 0), .struct_field_ptr_index_1 => try airStructFieldPtrIndex(o, inst, 1), @@ -1917,8 +1924,61 @@ fn airCmpxchg(o: *Object, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { return local; } -fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { - const str = switch (order) { +fn airAtomicRmw(o: *Object, inst: Air.Inst.Index) !CValue { + const pl_op = o.air.instructions.items(.data)[inst].pl_op; + const extra = o.air.extraData(Air.AtomicRmw, pl_op.payload).data; + const inst_ty = o.air.typeOfIndex(inst); + const ptr = try o.resolveInst(pl_op.operand); + const operand = try o.resolveInst(extra.operand); + const local = try o.allocLocal(inst_ty, .Const); + const writer = o.writer(); + + try writer.print(" = zig_atomicrmw_{s}(", .{toAtomicRmwSuffix(extra.op())}); + try o.writeCValue(writer, ptr); + try writer.writeAll(", "); + try o.writeCValue(writer, operand); + try writer.writeAll(", "); + try writeMemoryOrder(writer, extra.ordering()); + try writer.writeAll(");\n"); + + return local; +} + +fn airAtomicLoad(o: *Object, inst: Air.Inst.Index) !CValue { + const atomic_load = o.air.instructions.items(.data)[inst].atomic_load; + const inst_ty = o.air.typeOfIndex(inst); + const ptr = try o.resolveInst(atomic_load.ptr); + const local = try o.allocLocal(inst_ty, .Const); + const writer = o.writer(); + + try writer.writeAll(" = zig_atomic_load("); + try o.writeCValue(writer, ptr); + try writer.writeAll(", "); + try writeMemoryOrder(writer, atomic_load.order); + try writer.writeAll(");\n"); + + return local; +} + +fn airAtomicStore(o: *Object, inst: Air.Inst.Index, order: [*:0]const u8) !CValue { + const bin_op = o.air.instructions.items(.data)[inst].bin_op; + const ptr = try o.resolveInst(bin_op.lhs); + const element = try o.resolveInst(bin_op.rhs); + const inst_ty = o.air.typeOfIndex(inst); + const local = try o.allocLocal(inst_ty, .Const); + const writer = o.writer(); + + try writer.writeAll(" = zig_atomic_store("); + try o.writeCValue(writer, ptr); + try writer.writeAll(", "); + try o.writeCValue(writer, element); + try writer.print(", {s});\n", .{order}); + + return local; +} + +fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { + return switch (order) { .Unordered => "memory_order_relaxed", .Monotonic => "memory_order_consume", .Acquire => "memory_order_acquire", @@ -1926,7 +1986,24 @@ fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { .AcqRel => "memory_order_acq_rel", .SeqCst => "memory_order_seq_cst", }; - return w.writeAll(str); +} + +fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { + return w.writeAll(toMemoryOrder(order)); +} + +fn toAtomicRmwSuffix(order: std.builtin.AtomicRmwOp) []const u8 { + return switch (order) { + .Xchg => "xchg", + .Add => "add", + .Sub => "sub", + .And => "and", + .Nand => "nand", + .Or => "or", + .Xor => "xor", + .Max => "max", + .Min => "min", + }; } fn IndentWriter(comptime UnderlyingWriter: type) type { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 569f857caa..af25ae51b1 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1060,6 +1060,13 @@ pub const FuncGen = struct { .cmpxchg_weak => try self.airCmpxchg(inst, true), .cmpxchg_strong => try self.airCmpxchg(inst, false), .fence => try self.airFence(inst), + .atomic_rmw => try self.airAtomicRmw(inst), + .atomic_load => try self.airAtomicLoad(inst), + + .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), + .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), + .atomic_store_release => try self.airAtomicStore(inst, .Release), + .atomic_store_seq_cst => try self.airAtomicStore(inst, .SequentiallyConsistent), .struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), @@ -1983,20 +1990,19 @@ pub const FuncGen = struct { fn airStore(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try self.resolveInst(bin_op.lhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); const src_operand = try self.resolveInst(bin_op.rhs); - // TODO set volatile on this store properly - _ = self.builder.buildStore(src_operand, dest_ptr); + _ = self.store(dest_ptr, ptr_ty, src_operand); return null; } fn airLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const is_volatile = self.air.typeOf(ty_op.operand).isVolatilePtr(); - if (!is_volatile and self.liveness.isUnused(inst)) + const ptr_ty = self.air.typeOf(ty_op.operand); + if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; const ptr = try self.resolveInst(ty_op.operand); - // TODO set volatile on this load properly - return self.builder.buildLoad(ptr, ""); + return self.load(ptr, ptr_ty); } fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -2033,14 +2039,12 @@ pub const FuncGen = struct { new_value = self.builder.buildZExt(new_value, abi_ty, ""); } } - const success_order = toLlvmAtomicOrdering(extra.successOrder()); - const failure_order = toLlvmAtomicOrdering(extra.failureOrder()); const result = self.builder.buildCmpXchg( ptr, expected_value, new_value, - success_order, - failure_order, + toLlvmAtomicOrdering(extra.successOrder()), + toLlvmAtomicOrdering(extra.failureOrder()), is_weak, self.single_threaded, ); @@ -2066,6 +2070,109 @@ pub const FuncGen = struct { return self.builder.buildInsertValue(partial, non_null_bit, 1, ""); } + fn airAtomicRmw(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.AtomicRmw, pl_op.payload).data; + const ptr = try self.resolveInst(pl_op.operand); + const ptr_ty = self.air.typeOf(pl_op.operand); + const operand_ty = ptr_ty.elemType(); + const operand = try self.resolveInst(extra.operand); + const is_signed_int = operand_ty.isSignedInt(); + const is_float = operand_ty.isFloat(); + const op = toLlvmAtomicRmwBinOp(extra.op(), is_signed_int, is_float); + const ordering = toLlvmAtomicOrdering(extra.ordering()); + const single_threaded = llvm.Bool.fromBool(self.single_threaded); + const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, op == .Xchg); + if (opt_abi_ty) |abi_ty| { + // operand needs widening and truncating or bitcasting. + const casted_ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), ""); + const casted_operand = if (is_float) + self.builder.buildBitCast(operand, abi_ty, "") + else if (is_signed_int) + self.builder.buildSExt(operand, abi_ty, "") + else + self.builder.buildZExt(operand, abi_ty, ""); + + const uncasted_result = self.builder.buildAtomicRmw( + op, + casted_ptr, + casted_operand, + ordering, + single_threaded, + ); + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + if (is_float) { + return self.builder.buildBitCast(uncasted_result, operand_llvm_ty, ""); + } else { + return self.builder.buildTrunc(uncasted_result, operand_llvm_ty, ""); + } + } + + if (operand.typeOf().getTypeKind() != .Pointer) { + return self.builder.buildAtomicRmw(op, ptr, operand, ordering, single_threaded); + } + + // It's a pointer but we need to treat it as an int. + const usize_llvm_ty = try self.dg.llvmType(Type.initTag(.usize)); + const casted_ptr = self.builder.buildBitCast(ptr, usize_llvm_ty.pointerType(0), ""); + const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, ""); + const uncasted_result = self.builder.buildAtomicRmw( + op, + casted_ptr, + casted_operand, + ordering, + single_threaded, + ); + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + return self.builder.buildIntToPtr(uncasted_result, operand_llvm_ty, ""); + } + + fn airAtomicLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + 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); + const ordering = toLlvmAtomicOrdering(atomic_load.order); + const operand_ty = ptr_ty.elemType(); + const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false); + + if (opt_abi_ty) |abi_ty| { + // operand needs widening and truncating + const casted_ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), ""); + const load_inst = self.load(casted_ptr, ptr_ty); + load_inst.setOrdering(ordering); + return self.builder.buildTrunc(load_inst, try self.dg.llvmType(operand_ty), ""); + } + const load_inst = self.load(ptr, ptr_ty); + load_inst.setOrdering(ordering); + return load_inst; + } + + fn airAtomicStore( + self: *FuncGen, + inst: Air.Inst.Index, + ordering: llvm.AtomicOrdering, + ) !?*const llvm.Value { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + var ptr = try self.resolveInst(bin_op.lhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); + var element = try self.resolveInst(bin_op.rhs); + const operand_ty = ptr_ty.elemType(); + const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false); + + if (opt_abi_ty) |abi_ty| { + // operand needs widening + ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), ""); + if (operand_ty.isSignedInt()) { + element = self.builder.buildSExt(element, abi_ty, ""); + } else { + element = self.builder.buildZExt(element, abi_ty, ""); + } + } + const store_inst = self.store(ptr, ptr_ty, element); + store_inst.setOrdering(ordering); + return null; + } + fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value { const id = llvm.lookupIntrinsicID(name.ptr, name.len); assert(id != 0); @@ -2074,6 +2181,21 @@ pub const FuncGen = struct { // `getIntrinsicDeclaration` return self.llvmModule().getIntrinsicDeclaration(id, null, 0); } + + fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) *const llvm.Value { + _ = ptr_ty; // TODO set volatile and alignment on this load properly + return self.builder.buildLoad(ptr, ""); + } + + fn store( + self: *FuncGen, + ptr: *const llvm.Value, + ptr_ty: Type, + elem: *const llvm.Value, + ) *const llvm.Value { + _ = ptr_ty; // TODO set volatile and alignment on this store properly + return self.builder.buildStore(elem, ptr); + } }; fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { @@ -2227,3 +2349,21 @@ fn toLlvmAtomicOrdering(atomic_order: std.builtin.AtomicOrder) llvm.AtomicOrderi .SeqCst => .SequentiallyConsistent, }; } + +fn toLlvmAtomicRmwBinOp( + op: std.builtin.AtomicRmwOp, + is_signed: bool, + is_float: bool, +) llvm.AtomicRMWBinOp { + return switch (op) { + .Xchg => .Xchg, + .Add => if (is_float) llvm.AtomicRMWBinOp.FAdd else return .Add, + .Sub => if (is_float) llvm.AtomicRMWBinOp.FSub else return .Sub, + .And => .And, + .Nand => .Nand, + .Or => .Or, + .Xor => .Xor, + .Max => if (is_signed) llvm.AtomicRMWBinOp.Max else return .UMax, + .Min => if (is_signed) llvm.AtomicRMWBinOp.Min else return .UMin, + }; +} diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 3fed3ca879..3bbc24e174 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -133,6 +133,9 @@ pub const Value = opaque { pub const constIntToPtr = LLVMConstIntToPtr; extern fn LLVMConstIntToPtr(ConstantVal: *const Value, ToType: *const Type) *const Value; + + pub const setOrdering = LLVMSetOrdering; + extern fn LLVMSetOrdering(MemoryAccessInst: *const Value, Ordering: AtomicOrdering) void; }; pub const Type = opaque { @@ -167,6 +170,9 @@ pub const Type = opaque { ElementCount: c_uint, Packed: Bool, ) void; + + pub const getTypeKind = LLVMGetTypeKind; + extern fn LLVMGetTypeKind(Ty: *const Type) TypeKind; }; pub const Module = opaque { @@ -477,6 +483,14 @@ pub const Builder = opaque { Name: [*:0]const u8, ) *const Value; + pub const buildIntToPtr = LLVMBuildIntToPtr; + extern fn LLVMBuildIntToPtr( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; + pub const buildStructGEP = LLVMBuildStructGEP; extern fn LLVMBuildStructGEP( B: *const Builder, @@ -530,6 +544,16 @@ pub const Builder = opaque { singleThread: Bool, Name: [*:0]const u8, ) *const Value; + + pub const buildAtomicRmw = LLVMBuildAtomicRMW; + extern fn LLVMBuildAtomicRMW( + B: *const Builder, + op: AtomicRMWBinOp, + PTR: *const Value, + Val: *const Value, + ordering: AtomicOrdering, + singleThread: Bool, + ) *const Value; }; pub const IntPredicate = enum(c_uint) { @@ -901,3 +925,42 @@ pub const AtomicOrdering = enum(c_uint) { AcquireRelease = 6, SequentiallyConsistent = 7, }; + +pub const AtomicRMWBinOp = enum(c_int) { + Xchg, + Add, + Sub, + And, + Nand, + Or, + Xor, + Max, + Min, + UMax, + UMin, + FAdd, + FSub, +}; + +pub const TypeKind = enum(c_int) { + Void, + Half, + Float, + Double, + X86_FP80, + FP128, + PPC_FP128, + Label, + Integer, + Function, + Struct, + Array, + Pointer, + Vector, + Metadata, + X86_MMX, + Token, + ScalableVector, + BFloat, + X86_AMX, +}; diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 28d6f2dd17..e19a138c1b 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -62,16 +62,61 @@ #if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) #include{#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,55 +2718,30 @@ 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( +fn assignShift( gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index, - op_inst_tag: Zir.Inst.Extended, - comptime T: type, + 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 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), + const rhs_type = try gz.addUnNode(.typeof_log2_int_type, 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 assignShift( - gz: *GenZir, - scope: *Scope, - infix_node: Ast.Node.Index, - op_inst_tag: Zir.Inst.Tag, -) InnerError!void { +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; @@ -2771,10 +2749,10 @@ fn assignShift( 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_log2_int_type, lhs, infix_node); - const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs); + // 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(op_inst_tag, infix_node, Zir.Inst.Bin{ + const result = try gz.addPlNode(.shl_sat, infix_node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs, }); @@ -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 22:38:51 -0700 Subject: 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(-) (limited to 'src/codegen/c.zig') 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 { -- cgit v1.2.3