From 42aa1ea115eca3dcc704eddf020ce87271a41174 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 24 Sep 2021 17:33:06 -0700 Subject: [PATCH] stage2: implement `@memset` and `@memcpy` builtins --- src/Air.zig | 15 ++++ src/AstGen.zig | 16 ++--- src/Liveness.zig | 5 ++ src/Sema.zig | 126 ++++++++++++++++++++++++++++++++-- src/codegen.zig | 12 ++++ src/codegen/c.zig | 46 ++++++++++++- src/codegen/llvm.zig | 53 ++++++++++++++ src/codegen/llvm/bindings.zig | 19 +++++ src/link/C/zig.h | 1 + src/print_air.zig | 24 +++++++ src/print_zir.zig | 30 +++++++- src/type.zig | 73 +++++++++++++++++--- test/behavior/basic.zig | 18 +++++ test/behavior/misc.zig | 10 --- 14 files changed, 412 insertions(+), 36 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index c3181fac60..4341271f3a 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -321,6 +321,19 @@ pub const Inst = struct { /// Uses the `ty_op` field. int_to_float, + /// Given dest ptr, value, and len, set all elements at dest to value. + /// Result type is always void. + /// Uses the `pl_op` field. Operand is the dest ptr. Payload is `Bin`. `lhs` is the + /// value, `rhs` is the length. + /// The element type may be any type, not just u8. + memset, + /// Given dest ptr, src ptr, and len, copy len elements from src to dest. + /// Result type is always void. + /// Uses the `pl_op` field. Operand is the dest ptr. Payload is `Bin`. `lhs` is the + /// src ptr, `rhs` is the length. + /// The element type may be any type, not just u8. + memcpy, + /// Uses the `ty_pl` field with payload `Cmpxchg`. cmpxchg_weak, /// Uses the `ty_pl` field with payload `Cmpxchg`. @@ -628,6 +641,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .atomic_store_monotonic, .atomic_store_release, .atomic_store_seq_cst, + .memset, + .memcpy, => return Type.initTag(.void), .ptrtoint, diff --git a/src/AstGen.zig b/src/AstGen.zig index 416584bee9..469e77037a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2149,8 +2149,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .field_ptr_type, .field_parent_ptr, .maximum, - .memcpy, - .memset, .minimum, .builtin_async_call, .c_import, @@ -2204,6 +2202,8 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .set_float_mode, .set_runtime_safety, .closure_capture, + .memcpy, + .memset, => break :b true, } } else switch (maybe_unused_result) { @@ -7576,17 +7576,17 @@ fn builtinCall( }, .memcpy => { const result = try gz.addPlNode(.memcpy, node, Zir.Inst.Memcpy{ - .dest = try expr(gz, scope, .{ .ty = .manyptr_u8_type }, params[0]), - .source = try expr(gz, scope, .{ .ty = .manyptr_const_u8_type }, params[1]), - .byte_count = try expr(gz, scope, .{ .ty = .usize_type }, params[2]), + .dest = try expr(gz, scope, .{ .coerced_ty = .manyptr_u8_type }, params[0]), + .source = try expr(gz, scope, .{ .coerced_ty = .manyptr_const_u8_type }, params[1]), + .byte_count = try expr(gz, scope, .{ .coerced_ty = .usize_type }, params[2]), }); return rvalue(gz, rl, result, node); }, .memset => { const result = try gz.addPlNode(.memset, node, Zir.Inst.Memset{ - .dest = try expr(gz, scope, .{ .ty = .manyptr_u8_type }, params[0]), - .byte = try expr(gz, scope, .{ .ty = .u8_type }, params[1]), - .byte_count = try expr(gz, scope, .{ .ty = .usize_type }, params[2]), + .dest = try expr(gz, scope, .{ .coerced_ty = .manyptr_u8_type }, params[0]), + .byte = try expr(gz, scope, .{ .coerced_ty = .u8_type }, params[1]), + .byte_count = try expr(gz, scope, .{ .coerced_ty = .usize_type }, params[2]), }); return rvalue(gz, rl, result, node); }, diff --git a/src/Liveness.zig b/src/Liveness.zig index 4da5eaa284..42ab1ab351 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -361,6 +361,11 @@ fn analyzeInst( const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none }); }, + .memset, .memcpy => { + const pl_op = inst_datas[inst].pl_op; + const extra = a.air.extraData(Air.Bin, pl_op.payload).data; + return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs }); + }, .br => { const br = inst_datas[inst].br; return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 87cde2ca1a..41fabbfacd 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -341,8 +341,6 @@ pub fn analyzeBody( .field_ptr_type => try sema.zirFieldPtrType(block, inst), .field_parent_ptr => try sema.zirFieldParentPtr(block, inst), .maximum => try sema.zirMaximum(block, inst), - .memcpy => try sema.zirMemcpy(block, inst), - .memset => try sema.zirMemset(block, inst), .minimum => try sema.zirMinimum(block, inst), .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst), .@"resume" => try sema.zirResume(block, inst), @@ -526,6 +524,16 @@ pub fn analyzeBody( i += 1; continue; }, + .memcpy => { + try sema.zirMemcpy(block, inst); + i += 1; + continue; + }, + .memset => { + try sema.zirMemset(block, inst); + i += 1; + continue; + }, // Special case instructions to handle comptime control flow. .@"break" => { @@ -8422,16 +8430,119 @@ fn zirMaximum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr return sema.mod.fail(&block.base, src, "TODO: Sema.zirMaximum", .{}); } -fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.Memcpy, inst_data.payload_index).data; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemcpy", .{}); + const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const src_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const dest_ptr = sema.resolveInst(extra.dest); + const dest_ptr_ty = sema.typeOf(dest_ptr); + + if (dest_ptr_ty.zigTypeTag() != .Pointer) { + return sema.mod.fail(&block.base, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty}); + } + if (dest_ptr_ty.isConstPtr()) { + return sema.mod.fail(&block.base, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty}); + } + + const uncasted_src_ptr = sema.resolveInst(extra.source); + const uncasted_src_ptr_ty = sema.typeOf(uncasted_src_ptr); + if (uncasted_src_ptr_ty.zigTypeTag() != .Pointer) { + return sema.mod.fail(&block.base, src_src, "expected pointer, found '{}'", .{ + uncasted_src_ptr_ty, + }); + } + const src_ptr_info = uncasted_src_ptr_ty.ptrInfo().data; + const wanted_src_ptr_ty = try Module.ptrType( + sema.arena, + dest_ptr_ty.elemType2(), + null, + src_ptr_info.@"align", + src_ptr_info.@"addrspace", + 0, + 0, + false, + src_ptr_info.@"allowzero", + src_ptr_info.@"volatile", + .Many, + ); + const src_ptr = try sema.coerce(block, wanted_src_ptr_ty, uncasted_src_ptr, src_src); + const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src); + + const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr); + const maybe_src_ptr_val = try sema.resolveDefinedValue(block, src_src, src_ptr); + const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len); + + const runtime_src = if (maybe_dest_ptr_val) |dest_ptr_val| rs: { + if (maybe_src_ptr_val) |src_ptr_val| { + if (maybe_len_val) |len_val| { + _ = dest_ptr_val; + _ = src_ptr_val; + _ = len_val; + return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemcpy at comptime", .{}); + } else break :rs len_src; + } else break :rs src_src; + } else dest_src; + + try sema.requireRuntimeBlock(block, runtime_src); + _ = try block.addInst(.{ + .tag = .memcpy, + .data = .{ .pl_op = .{ + .operand = dest_ptr, + .payload = try sema.addExtra(Air.Bin{ + .lhs = src_ptr, + .rhs = len, + }), + } }, + }); } -fn zirMemset(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirMemset(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.Memset, inst_data.payload_index).data; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemset", .{}); + const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const value_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const dest_ptr = sema.resolveInst(extra.dest); + const dest_ptr_ty = sema.typeOf(dest_ptr); + if (dest_ptr_ty.zigTypeTag() != .Pointer) { + return sema.mod.fail(&block.base, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty}); + } + if (dest_ptr_ty.isConstPtr()) { + return sema.mod.fail(&block.base, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty}); + } + const elem_ty = dest_ptr_ty.elemType2(); + const value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.byte), value_src); + const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src); + + const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr); + const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len); + + const runtime_src = if (maybe_dest_ptr_val) |ptr_val| rs: { + if (maybe_len_val) |len_val| { + if (try sema.resolveMaybeUndefVal(block, value_src, value)) |val| { + _ = ptr_val; + _ = len_val; + _ = val; + return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemset at comptime", .{}); + } else break :rs value_src; + } else break :rs len_src; + } else dest_src; + + try sema.requireRuntimeBlock(block, runtime_src); + _ = try block.addInst(.{ + .tag = .memset, + .data = .{ .pl_op = .{ + .operand = dest_ptr, + .payload = try sema.addExtra(Air.Bin{ + .lhs = value, + .rhs = len, + }), + } }, + }); } fn zirMinimum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -10090,7 +10201,8 @@ fn coerceArrayPtrToMany( // The comptime Value representation is compatible with both types. return sema.addConstant(dest_type, val); } - return sema.mod.fail(&block.base, inst_src, "TODO implement coerceArrayPtrToMany runtime instruction", .{}); + try sema.requireRuntimeBlock(block, inst_src); + return sema.bitcast(block, dest_type, inst, inst_src); } fn analyzeDeclVal( diff --git a/src/codegen.zig b/src/codegen.zig index f812cbc5d4..102f8d4985 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -887,6 +887,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .cmpxchg_weak => try self.airCmpxchg(inst), .atomic_rmw => try self.airAtomicRmw(inst), .atomic_load => try self.airAtomicLoad(inst), + .memcpy => try self.airMemcpy(inst), + .memset => try self.airMemset(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -4883,6 +4885,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch}); } + fn airMemset(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch}); + } + + fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch}); + } + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a82f0e57f7..16b13db292 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -953,6 +953,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .cmpxchg_strong => try airCmpxchg(f, inst, "strong"), .atomic_rmw => try airAtomicRmw(f, inst), .atomic_load => try airAtomicLoad(f, inst), + .memset => try airMemset(f, inst), + .memcpy => try airMemcpy(f, inst), .int_to_float, .float_to_int, @@ -2005,8 +2007,12 @@ fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue { fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue { const atomic_load = f.air.instructions.items(.data)[inst].atomic_load; - const inst_ty = f.air.typeOfIndex(inst); const ptr = try f.resolveInst(atomic_load.ptr); + const ptr_ty = f.air.typeOf(atomic_load.ptr); + if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) + return CValue.none; + + const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); @@ -2036,6 +2042,44 @@ fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CVa return local; } +fn airMemset(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const extra = f.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try f.resolveInst(pl_op.operand); + const value = try f.resolveInst(extra.lhs); + const len = try f.resolveInst(extra.rhs); + const writer = f.object.writer(); + + try writer.writeAll("memset("); + try f.writeCValue(writer, dest_ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, value); + try writer.writeAll(", "); + try f.writeCValue(writer, len); + try writer.writeAll(");\n"); + + return CValue.none; +} + +fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const extra = f.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try f.resolveInst(pl_op.operand); + const src_ptr = try f.resolveInst(extra.lhs); + const len = try f.resolveInst(extra.rhs); + const writer = f.object.writer(); + + try writer.writeAll("memcpy("); + try f.writeCValue(writer, dest_ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, src_ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, len); + try writer.writeAll(");\n"); + + return CValue.none; +} + fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { .Unordered => "memory_order_relaxed", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b2b87b97d8..b15834c963 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1279,6 +1279,8 @@ pub const FuncGen = struct { .fence => try self.airFence(inst), .atomic_rmw => try self.airAtomicRmw(inst), .atomic_load => try self.airAtomicLoad(inst), + .memset => try self.airMemset(inst), + .memcpy => try self.airMemcpy(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -2426,6 +2428,8 @@ pub const FuncGen = struct { const atomic_load = self.air.instructions.items(.data)[inst].atomic_load; const ptr = try self.resolveInst(atomic_load.ptr); const ptr_ty = self.air.typeOf(atomic_load.ptr); + if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) + return null; const ordering = toLlvmAtomicOrdering(atomic_load.order); const operand_ty = ptr_ty.elemType(); const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false); @@ -2468,6 +2472,55 @@ pub const FuncGen = struct { return null; } + fn airMemset(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try self.resolveInst(pl_op.operand); + const ptr_ty = self.air.typeOf(pl_op.operand); + const value = try self.resolveInst(extra.lhs); + const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndef() else false; + const len = try self.resolveInst(extra.rhs); + const u8_llvm_ty = self.context.intType(8); + const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); + const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, ""); + const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value; + const target = self.dg.module.getTarget(); + const dest_ptr_align = ptr_ty.ptrAlignment(target); + const memset = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align); + memset.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr())); + + if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) { + // TODO generate valgrind client request to mark byte range as undefined + // see gen_valgrind_undef() in codegen.cpp + } + return null; + } + + fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try self.resolveInst(pl_op.operand); + const dest_ptr_ty = self.air.typeOf(pl_op.operand); + const src_ptr = try self.resolveInst(extra.lhs); + const src_ptr_ty = self.air.typeOf(extra.lhs); + const len = try self.resolveInst(extra.rhs); + const u8_llvm_ty = self.context.intType(8); + const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); + const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, ""); + const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, ""); + const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr(); + const target = self.dg.module.getTarget(); + const memcpy = self.builder.buildMemCpy( + dest_ptr_u8, + dest_ptr_ty.ptrAlignment(target), + src_ptr_u8, + src_ptr_ty.ptrAlignment(target), + len, + ); + memcpy.setVolatile(llvm.Bool.fromBool(is_volatile)); + return null; + } + fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value { const id = llvm.lookupIntrinsicID(name.ptr, name.len); assert(id != 0); diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index c53ac08fdd..9d32682260 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -632,6 +632,25 @@ pub const Builder = opaque { DestTy: *const Type, Name: [*:0]const u8, ) *const Value; + + pub const buildMemSet = LLVMBuildMemSet; + extern fn LLVMBuildMemSet( + B: *const Builder, + Ptr: *const Value, + Val: *const Value, + Len: *const Value, + Align: c_uint, + ) *const Value; + + pub const buildMemCpy = LLVMBuildMemCpy; + extern fn LLVMBuildMemCpy( + B: *const Builder, + Dst: *const Value, + DstAlign: c_uint, + Src: *const Value, + SrcAlign: c_uint, + Size: *const Value, + ) *const Value; }; pub const IntPredicate = enum(c_uint) { diff --git a/src/link/C/zig.h b/src/link/C/zig.h index e19a138c1b..b34068d1f2 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -126,6 +126,7 @@ #define int128_t __int128 #define uint128_t unsigned __int128 ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t); +ZIG_EXTERN_C void *memset (void *, int, size_t); static inline uint8_t zig_addw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) { uint8_t thresh = max - rhs; diff --git a/src/print_air.zig b/src/print_air.zig index a9ad993eb0..fa384baae0 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -202,6 +202,8 @@ const Writer = struct { .atomic_store_release => try w.writeAtomicStore(s, inst, .Release), .atomic_store_seq_cst => try w.writeAtomicStore(s, inst, .SeqCst), .atomic_rmw => try w.writeAtomicRmw(s, inst), + .memcpy => try w.writeMemcpy(s, inst), + .memset => try w.writeMemset(s, inst), } } @@ -322,6 +324,28 @@ const Writer = struct { try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) }); } + fn writeMemset(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const pl_op = w.air.instructions.items(.data)[inst].pl_op; + const extra = w.air.extraData(Air.Bin, pl_op.payload).data; + + try w.writeOperand(s, inst, 0, pl_op.operand); + try s.writeAll(", "); + try w.writeOperand(s, inst, 1, extra.lhs); + try s.writeAll(", "); + try w.writeOperand(s, inst, 2, extra.rhs); + } + + fn writeMemcpy(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const pl_op = w.air.instructions.items(.data)[inst].pl_op; + const extra = w.air.extraData(Air.Bin, pl_op.payload).data; + + try w.writeOperand(s, inst, 0, pl_op.operand); + try s.writeAll(", "); + try w.writeOperand(s, inst, 1, extra.lhs); + try s.writeAll(", "); + try w.writeOperand(s, inst, 2, extra.rhs); + } + fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const val = w.air.values[ty_pl.payload]; diff --git a/src/print_zir.zig b/src/print_zir.zig index 9350fd0de3..6ae218ed22 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -210,8 +210,6 @@ const Writer = struct { .mul_add, .builtin_call, .field_parent_ptr, - .memcpy, - .memset, .builtin_async_call, => try self.writePlNode(stream, inst), @@ -222,6 +220,8 @@ const Writer = struct { .cmpxchg_strong, .cmpxchg_weak => try self.writeCmpxchg(stream, inst), .atomic_store => try self.writeAtomicStore(stream, inst), .atomic_rmw => try self.writeAtomicRmw(stream, inst), + .memcpy => try self.writeMemcpy(stream, inst), + .memset => try self.writeMemset(stream, inst), .struct_init_anon, .struct_init_anon_ref, @@ -692,6 +692,32 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writeMemcpy(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Memcpy, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.dest); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.source); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.byte_count); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeMemset(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Memset, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.dest); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.byte); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.byte_count); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeStructInitAnon(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.StructInitAnon, inst_data.payload_index); diff --git a/src/type.zig b/src/type.zig index a2da252f61..0381111345 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2391,12 +2391,11 @@ pub const Type = extern union { }; } - /// Asserts the type is a pointer or array type. - pub fn elemType(self: Type) Type { - return switch (self.tag()) { - .vector => self.castTag(.vector).?.data.elem_type, - .array => self.castTag(.array).?.data.elem_type, - .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type, + pub fn childType(ty: Type) Type { + return switch (ty.tag()) { + .vector => ty.castTag(.vector).?.data.elem_type, + .array => ty.castTag(.array).?.data.elem_type, + .array_sentinel => ty.castTag(.array_sentinel).?.data.elem_type, .single_const_pointer, .single_mut_pointer, .many_const_pointer, @@ -2405,7 +2404,7 @@ pub const Type = extern union { .c_mut_pointer, .const_slice, .mut_slice, - => self.castPointer().?.data, + => ty.castPointer().?.data, .array_u8, .array_u8_sentinel_0, @@ -2415,12 +2414,70 @@ pub const Type = extern union { => Type.initTag(.u8), .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), - .pointer => self.castTag(.pointer).?.data.pointee_type, + .pointer => ty.castTag(.pointer).?.data.pointee_type, else => unreachable, }; } + /// Asserts the type is a pointer or array type. + /// TODO this is deprecated in favor of `childType`. + pub const elemType = childType; + + /// For *[N]T, returns T. + /// For ?*T, returns T. + /// For ?*[N]T, returns T. + /// For ?[*]T, returns T. + /// For *T, returns T. + /// For [*]T, returns T. + pub fn elemType2(ty: Type) Type { + return switch (ty.tag()) { + .vector => ty.castTag(.vector).?.data.elem_type, + .array => ty.castTag(.array).?.data.elem_type, + .array_sentinel => ty.castTag(.array_sentinel).?.data.elem_type, + .many_const_pointer, + .many_mut_pointer, + .c_const_pointer, + .c_mut_pointer, + .const_slice, + .mut_slice, + => ty.castPointer().?.data, + + .single_const_pointer, + .single_mut_pointer, + => ty.castPointer().?.data.shallowElemType(), + + .array_u8, + .array_u8_sentinel_0, + .const_slice_u8, + .manyptr_u8, + .manyptr_const_u8, + => Type.initTag(.u8), + + .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), + .pointer => { + const info = ty.castTag(.pointer).?.data; + const child_ty = info.pointee_type; + if (info.size == .One) { + return child_ty.shallowElemType(); + } else { + return child_ty; + } + }, + + // TODO handle optionals + + else => unreachable, + }; + } + + fn shallowElemType(child_ty: Type) Type { + return switch (child_ty.zigTypeTag()) { + .Array, .Vector => child_ty.childType(), + else => child_ty, + }; + } + /// Asserts that the type is an optional. /// Resulting `Type` will have inner memory referencing `buf`. pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type { diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 517162c8d4..f6876e29ad 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -170,3 +170,21 @@ test "string concatenation" { test "array mult operator" { try expect(mem.eql(u8, "ab" ** 5, "ababababab")); } + +test "memcpy and memset intrinsics" { + try testMemcpyMemset(); + // TODO add comptime test coverage + //comptime try testMemcpyMemset(); +} + +fn testMemcpyMemset() !void { + var foo: [20]u8 = undefined; + var bar: [20]u8 = undefined; + + @memset(&foo, 'A', foo.len); + @memcpy(&bar, &foo, bar.len); + + try expect(bar[0] == 'A'); + try expect(bar[11] == 'A'); + try expect(bar[19] == 'A'); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index 5394e6fd14..9b3bf48366 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -5,16 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; const mem = std.mem; const builtin = @import("builtin"); -test "memcpy and memset intrinsics" { - var foo: [20]u8 = undefined; - var bar: [20]u8 = undefined; - - @memset(&foo, 'A', foo.len); - @memcpy(&bar, &foo, bar.len); - - if (bar[11] != 'A') unreachable; -} - test "slicing" { var array: [20]i32 = undefined;