Commit 83f69af971

Andrew Kelley <andrew@ziglang.org>
2022-05-26 07:23:32
stage2: implement runtime array multiplication
Additionally: * Sema: fix array cat/mul not setting the sentinel value - This required an LLVM backend enhancement to the handling of the AIR instruction aggregate_init that likely needs to be propagated to the other backends. * Sema: report integer overflow of array concatenation in a proper compile error instead of crashing. * Sema: fix not using proper pointer address space for array cat/mul
1 parent b82081e
Changed files (4)
src
test
behavior
src/codegen/llvm.zig
@@ -7728,7 +7728,14 @@ pub const FuncGen = struct {
                 const alloca_inst = self.buildAlloca(llvm_result_ty);
                 alloca_inst.setAlignment(result_ty.abiAlignment(target));
 
-                const elem_ty = result_ty.childType();
+                const array_info = result_ty.arrayInfo();
+                var elem_ptr_payload: Type.Payload.Pointer = .{
+                    .data = .{
+                        .pointee_type = array_info.elem_type,
+                        .@"addrspace" = .generic,
+                    },
+                };
+                const elem_ptr_ty = Type.initPayload(&elem_ptr_payload.base);
 
                 for (elements) |elem, i| {
                     const indices: [2]*const llvm.Value = .{
@@ -7737,13 +7744,19 @@ pub const FuncGen = struct {
                     };
                     const elem_ptr = self.builder.buildInBoundsGEP(alloca_inst, &indices, indices.len, "");
                     const llvm_elem = try self.resolveInst(elem);
-                    var elem_ptr_payload: Type.Payload.Pointer = .{
-                        .data = .{
-                            .pointee_type = elem_ty,
-                            .@"addrspace" = .generic,
-                        },
+                    self.store(elem_ptr, elem_ptr_ty, llvm_elem, .NotAtomic);
+                }
+                if (array_info.sentinel) |sent_val| {
+                    const indices: [2]*const llvm.Value = .{
+                        llvm_usize.constNull(),
+                        llvm_usize.constInt(@intCast(c_uint, array_info.len), .False),
                     };
-                    const elem_ptr_ty = Type.initPayload(&elem_ptr_payload.base);
+                    const elem_ptr = self.builder.buildInBoundsGEP(alloca_inst, &indices, indices.len, "");
+                    const llvm_elem = try self.dg.lowerValue(.{
+                        .ty = array_info.elem_type,
+                        .val = sent_val,
+                    });
+
                     self.store(elem_ptr, elem_ptr_ty, llvm_elem, .NotAtomic);
                 }
 
src/Air.zig
@@ -609,6 +609,8 @@ pub const Inst = struct {
         /// Some of the elements may be comptime-known.
         /// Uses the `ty_pl` field, payload is index of an array of elements, each of which
         /// is a `Ref`. Length of the array is given by the vector type.
+        /// If the type is an array with a sentinel, the AIR elements do not include it
+        /// explicitly.
         aggregate_init,
 
         /// Constructs a union from a field index and a runtime-known init value.
src/Sema.zig
@@ -9264,10 +9264,8 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
     const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
 
-    const lhs_info = (try sema.getArrayCatInfo(block, lhs_src, lhs)) orelse
-        return sema.fail(block, lhs_src, "expected array, found '{}'", .{lhs_ty.fmt(sema.mod)});
-    const rhs_info = (try sema.getArrayCatInfo(block, rhs_src, rhs)) orelse
-        return sema.fail(block, rhs_src, "expected array, found '{}'", .{rhs_ty.fmt(sema.mod)});
+    const lhs_info = try sema.getArrayCatInfo(block, lhs_src, lhs);
+    const rhs_info = try sema.getArrayCatInfo(block, rhs_src, rhs);
 
     const resolved_elem_ty = t: {
         var trash_block = block.makeSubBlock();
@@ -9319,9 +9317,21 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
     const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len);
     const rhs_len = try sema.usizeCast(block, lhs_src, rhs_info.len);
-    const result_len = lhs_len + rhs_len;
+    const result_len = std.math.add(usize, lhs_len, rhs_len) catch |err| switch (err) {
+        error.Overflow => return sema.fail(
+            block,
+            src,
+            "concatenating arrays of length {d} and {d} produces an array too large for this compiler implementation to handle",
+            .{ lhs_len, rhs_len },
+        ),
+    };
+
     const result_ty = try Type.array(sema.arena, result_len, res_sent_val, resolved_elem_ty, sema.mod);
-    const is_ref = lhs_ty.zigTypeTag() == .Pointer or rhs_ty.zigTypeTag() == .Pointer;
+    const ptr_addrspace = p: {
+        if (lhs_ty.zigTypeTag() == .Pointer) break :p lhs_ty.ptrAddressSpace();
+        if (rhs_ty.zigTypeTag() == .Pointer) break :p rhs_ty.ptrAddressSpace();
+        break :p null;
+    };
 
     const runtime_src = if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| rs: {
         if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| {
@@ -9348,22 +9358,21 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 element_vals[result_len] = sent_val;
             }
             const val = try Value.Tag.aggregate.create(sema.arena, element_vals);
-            return sema.addConstantMaybeRef(block, src, result_ty, val, is_ref);
+            return sema.addConstantMaybeRef(block, src, result_ty, val, ptr_addrspace != null);
         } else break :rs rhs_src;
     } else lhs_src;
 
     try sema.requireRuntimeBlock(block, runtime_src);
 
-    if (is_ref) {
-        const target = sema.mod.getTarget();
+    if (ptr_addrspace) |ptr_as| {
         const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{
             .pointee_type = result_ty,
-            .@"addrspace" = target_util.defaultAddressSpace(target, .local),
+            .@"addrspace" = ptr_as,
         });
         const alloc = try block.addTy(.alloc, alloc_ty);
         const elem_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{
             .pointee_type = resolved_elem_ty,
-            .@"addrspace" = target_util.defaultAddressSpace(target, .local),
+            .@"addrspace" = ptr_as,
         });
 
         var elem_i: usize = 0;
@@ -9380,6 +9389,12 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             const init = try sema.elemVal(block, rhs_src, rhs, rhs_index, src);
             try sema.storePtr2(block, src, elem_ptr, src, init, rhs_src, .store);
         }
+        if (res_sent_val) |sent_val| {
+            const elem_index = try sema.addIntUnsigned(Type.usize, result_len);
+            const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty);
+            const init = try sema.addConstant(lhs_info.elem_type, sent_val);
+            try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store);
+        }
 
         return alloc;
     }
@@ -9402,30 +9417,35 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     return block.addAggregateInit(result_ty, element_refs);
 }
 
-fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, inst: Air.Inst.Ref) !?Type.ArrayInfo {
-    const t = sema.typeOf(inst);
-    return switch (t.zigTypeTag()) {
-        .Array => t.arrayInfo(),
-        .Pointer => blk: {
-            const ptrinfo = t.ptrInfo().data;
-            switch (ptrinfo.size) {
+fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Inst.Ref) !Type.ArrayInfo {
+    const operand_ty = sema.typeOf(operand);
+    switch (operand_ty.zigTypeTag()) {
+        .Array => return operand_ty.arrayInfo(),
+        .Pointer => {
+            const ptr_info = operand_ty.ptrInfo().data;
+            switch (ptr_info.size) {
+                // TODO: in the Many case here this should only work if the type
+                // has a sentinel, and this code should compute the length based
+                // on the sentinel value.
                 .Slice, .Many => {
-                    const val = try sema.resolveConstValue(block, src, inst);
+                    const val = try sema.resolveConstValue(block, src, operand);
                     return Type.ArrayInfo{
-                        .elem_type = t.childType(),
-                        .sentinel = t.sentinel(),
+                        .elem_type = ptr_info.pointee_type,
+                        .sentinel = ptr_info.sentinel,
                         .len = val.sliceLen(sema.mod),
                     };
                 },
                 .One => {
-                    if (ptrinfo.pointee_type.zigTypeTag() != .Array) return null;
-                    break :blk ptrinfo.pointee_type.arrayInfo();
+                    if (ptr_info.pointee_type.zigTypeTag() == .Array) {
+                        return ptr_info.pointee_type.arrayInfo();
+                    }
                 },
-                .C => return null,
+                .C => {},
             }
         },
-        else => null,
-    };
+        else => {},
+    }
+    return sema.fail(block, src, "expected indexable; found '{}'", .{operand_ty.fmt(sema.mod)});
 }
 
 fn analyzeTupleMul(
@@ -9514,65 +9534,99 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         return sema.analyzeTupleMul(block, inst_data.src_node, lhs, factor);
     }
 
-    const mulinfo = (try sema.getArrayCatInfo(block, lhs_src, lhs)) orelse
-        return sema.fail(block, lhs_src, "expected array, found '{}'", .{lhs_ty.fmt(sema.mod)});
+    const lhs_info = try sema.getArrayCatInfo(block, lhs_src, lhs);
 
-    const final_len_u64 = std.math.mul(u64, mulinfo.len, factor) catch
+    const result_len_u64 = std.math.mul(u64, lhs_info.len, factor) catch
         return sema.fail(block, rhs_src, "operation results in overflow", .{});
+    const result_len = try sema.usizeCast(block, src, result_len_u64);
 
-    if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
-        const final_len = try sema.usizeCast(block, src, final_len_u64);
-        const final_len_including_sent = final_len + @boolToInt(mulinfo.sentinel != null);
-        const lhs_len = try sema.usizeCast(block, lhs_src, mulinfo.len);
+    const result_ty = try Type.array(sema.arena, result_len, lhs_info.sentinel, lhs_info.elem_type, sema.mod);
 
-        const is_single_ptr = lhs_ty.zigTypeTag() == .Pointer and !lhs_ty.isSlice();
-        const lhs_sub_val = if (is_single_ptr) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val;
+    const ptr_addrspace = if (lhs_ty.zigTypeTag() == .Pointer) lhs_ty.ptrAddressSpace() else null;
+    const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len);
 
-        var anon_decl = try block.startAnonDecl(src);
-        defer anon_decl.deinit();
+    if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
+        const final_len_including_sent = result_len + @boolToInt(lhs_info.sentinel != null);
 
-        const final_ty = if (mulinfo.sentinel) |sent|
-            try Type.Tag.array_sentinel.create(anon_decl.arena(), .{
-                .len = final_len,
-                .elem_type = try mulinfo.elem_type.copy(anon_decl.arena()),
-                .sentinel = try sent.copy(anon_decl.arena()),
-            })
+        const lhs_sub_val = if (lhs_ty.isSinglePointer())
+            (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).?
         else
-            try Type.Tag.array.create(anon_decl.arena(), .{
-                .len = final_len,
-                .elem_type = try mulinfo.elem_type.copy(anon_decl.arena()),
-            });
-        const buf = try anon_decl.arena().alloc(Value, final_len_including_sent);
-
-        // Optimization for the common pattern of a single element repeated N times, such
-        // as zero-filling a byte array.
-        const val = if (lhs_len == 1) blk: {
-            const elem_val = try lhs_sub_val.elemValue(sema.mod, sema.arena, 0);
-            const copied_val = try elem_val.copy(anon_decl.arena());
-            break :blk try Value.Tag.repeated.create(anon_decl.arena(), copied_val);
-        } else blk: {
-            // the actual loop
-            var i: usize = 0;
-            while (i < factor) : (i += 1) {
-                var j: usize = 0;
-                while (j < lhs_len) : (j += 1) {
-                    const val = try lhs_sub_val.elemValue(sema.mod, sema.arena, j);
-                    buf[lhs_len * i + j] = try val.copy(anon_decl.arena());
+            lhs_val;
+
+        const val = v: {
+            // Optimization for the common pattern of a single element repeated N times, such
+            // as zero-filling a byte array.
+            if (lhs_len == 1) {
+                const elem_val = try lhs_sub_val.elemValue(sema.mod, sema.arena, 0);
+                break :v try Value.Tag.repeated.create(sema.arena, elem_val);
+            }
+
+            const element_vals = try sema.arena.alloc(Value, final_len_including_sent);
+            var elem_i: usize = 0;
+            while (elem_i < result_len) {
+                var lhs_i: usize = 0;
+                while (lhs_i < lhs_len) : (lhs_i += 1) {
+                    const elem_val = try lhs_sub_val.elemValue(sema.mod, sema.arena, lhs_i);
+                    element_vals[elem_i] = elem_val;
+                    elem_i += 1;
                 }
             }
-            if (mulinfo.sentinel) |sent| {
-                buf[final_len] = try sent.copy(anon_decl.arena());
+            if (lhs_info.sentinel) |sent_val| {
+                element_vals[result_len] = sent_val;
             }
-            break :blk try Value.Tag.aggregate.create(anon_decl.arena(), buf);
+            break :v try Value.Tag.aggregate.create(sema.arena, element_vals);
         };
-        const decl = try anon_decl.finish(final_ty, val, 0);
-        if (lhs_ty.zigTypeTag() == .Pointer) {
-            return sema.analyzeDeclRef(decl);
-        } else {
-            return sema.analyzeDeclVal(block, .unneeded, decl);
+        return sema.addConstantMaybeRef(block, src, result_ty, val, ptr_addrspace != null);
+    }
+
+    try sema.requireRuntimeBlock(block, lhs_src);
+
+    if (ptr_addrspace) |ptr_as| {
+        const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{
+            .pointee_type = result_ty,
+            .@"addrspace" = ptr_as,
+        });
+        const alloc = try block.addTy(.alloc, alloc_ty);
+        const elem_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{
+            .pointee_type = lhs_info.elem_type,
+            .@"addrspace" = ptr_as,
+        });
+
+        var elem_i: usize = 0;
+        while (elem_i < result_len) {
+            var lhs_i: usize = 0;
+            while (lhs_i < lhs_len) : (lhs_i += 1) {
+                const elem_index = try sema.addIntUnsigned(Type.usize, elem_i);
+                elem_i += 1;
+                const lhs_index = try sema.addIntUnsigned(Type.usize, lhs_i);
+                const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty);
+                const init = try sema.elemVal(block, lhs_src, lhs, lhs_index, src);
+                try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store);
+            }
+        }
+        if (lhs_info.sentinel) |sent_val| {
+            const elem_index = try sema.addIntUnsigned(Type.usize, result_len);
+            const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty);
+            const init = try sema.addConstant(lhs_info.elem_type, sent_val);
+            try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store);
+        }
+
+        return alloc;
+    }
+
+    const element_refs = try sema.arena.alloc(Air.Inst.Ref, result_len);
+    var elem_i: usize = 0;
+    while (elem_i < result_len) {
+        var lhs_i: usize = 0;
+        while (lhs_i < lhs_len) : (lhs_i += 1) {
+            const lhs_index = try sema.addIntUnsigned(Type.usize, lhs_i);
+            const init = try sema.elemVal(block, lhs_src, lhs, lhs_index, src);
+            element_refs[elem_i] = init;
+            elem_i += 1;
         }
     }
-    return sema.fail(block, lhs_src, "TODO runtime array_mul", .{});
+
+    return block.addAggregateInit(result_ty, element_refs);
 }
 
 fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
test/behavior/eval.zig
@@ -742,6 +742,23 @@ test "array concatenation of function calls" {
     try expect(std.mem.eql(i32, &a, &[_]i32{ 3, 4 }));
 }
 
+test "array multiplication of function calls" {
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+
+    var a = oneItem(3) ** scalar(2);
+    try expect(std.mem.eql(i32, &a, &[_]i32{ 3, 3 }));
+}
+
+fn oneItem(x: i32) [1]i32 {
+    return [_]i32{x};
+}
+
+fn scalar(x: u32) u32 {
+    return x;
+}
+
 test "array concatenation peer resolves element types - value" {
     if (builtin.zig_backend == .stage1) return error.SkipZigTest;
     if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
@@ -751,7 +768,7 @@ test "array concatenation peer resolves element types - value" {
     var a = [2]u3{ 1, 7 };
     var b = [3]u8{ 200, 225, 255 };
     var c = a ++ b;
-    try expect(@TypeOf(c) == [5]u8);
+    comptime assert(@TypeOf(c) == [5]u8);
     try expect(c[0] == 1);
     try expect(c[1] == 7);
     try expect(c[2] == 200);
@@ -768,7 +785,7 @@ test "array concatenation peer resolves element types - pointer" {
     var a = [2]u3{ 1, 7 };
     var b = [3]u8{ 200, 225, 255 };
     var c = &a ++ &b;
-    try expect(@TypeOf(c) == *[5]u8);
+    comptime assert(@TypeOf(c) == *[5]u8);
     try expect(c[0] == 1);
     try expect(c[1] == 7);
     try expect(c[2] == 200);
@@ -776,23 +793,80 @@ test "array concatenation peer resolves element types - pointer" {
     try expect(c[4] == 255);
 }
 
-test "array multiplication forces comptime" {
-    if (builtin.zig_backend != .stage1) {
-        // note: our plan is to change the language to support runtime array
-        // multiplication instead of making this test pass.
-        return error.SkipZigTest; // TODO
-    }
+test "array concatenation sets the sentinel - value" {
+    if (builtin.zig_backend == .stage1) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
 
-    var a = oneItem(3) ** scalar(2);
-    try expect(std.mem.eql(i32, &a, &[_]i32{ 3, 3 }));
+    var a = [2]u3{ 1, 7 };
+    var b = [3:69]u8{ 200, 225, 255 };
+    var c = a ++ b;
+    comptime assert(@TypeOf(c) == [5:69]u8);
+    try expect(c[0] == 1);
+    try expect(c[1] == 7);
+    try expect(c[2] == 200);
+    try expect(c[3] == 225);
+    try expect(c[4] == 255);
+    var ptr: [*]const u8 = &c;
+    try expect(ptr[5] == 69);
 }
 
-fn oneItem(x: i32) [1]i32 {
-    return [_]i32{x};
+test "array concatenation sets the sentinel - pointer" {
+    if (builtin.zig_backend == .stage1) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+
+    var a = [2]u3{ 1, 7 };
+    var b = [3:69]u8{ 200, 225, 255 };
+    var c = &a ++ &b;
+    comptime assert(@TypeOf(c) == *[5:69]u8);
+    try expect(c[0] == 1);
+    try expect(c[1] == 7);
+    try expect(c[2] == 200);
+    try expect(c[3] == 225);
+    try expect(c[4] == 255);
+    var ptr: [*]const u8 = c;
+    try expect(ptr[5] == 69);
 }
 
-fn scalar(x: u32) u32 {
-    return x;
+test "array multiplication sets the sentinel - value" {
+    if (builtin.zig_backend == .stage1) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+
+    var a = [2:7]u3{ 1, 6 };
+    var b = a ** 2;
+    comptime assert(@TypeOf(b) == [4:7]u3);
+    try expect(b[0] == 1);
+    try expect(b[1] == 6);
+    try expect(b[2] == 1);
+    try expect(b[3] == 6);
+    var ptr: [*]const u3 = &b;
+    try expect(ptr[4] == 7);
+}
+
+test "array multiplication sets the sentinel - pointer" {
+    if (builtin.zig_backend == .stage1) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+
+    var a = [2:7]u3{ 1, 6 };
+    var b = &a ** 2;
+    comptime assert(@TypeOf(b) == *[4:7]u3);
+    try expect(b[0] == 1);
+    try expect(b[1] == 6);
+    try expect(b[2] == 1);
+    try expect(b[3] == 6);
+    var ptr: [*]const u3 = b;
+    try expect(ptr[4] == 7);
 }
 
 test "comptime assign int to optional int" {