Commit b82081e709

Andrew Kelley <andrew@ziglang.org>
2022-05-26 05:29:12
Sema: implement array concatenation with runtime operands
1 parent ab88165
Changed files (2)
src
test
behavior
src/Sema.zig
@@ -9255,6 +9255,7 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const rhs = try sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
+    const src = inst_data.src();
 
     if (lhs_ty.isTuple() and rhs_ty.isTuple()) {
         return sema.analyzeTupleCat(block, inst_data.src_node, lhs, rhs);
@@ -9267,73 +9268,138 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         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)});
-    if (!lhs_info.elem_type.eql(rhs_info.elem_type, sema.mod)) {
-        return sema.fail(block, rhs_src, "expected array of type '{}', found '{}'", .{
-            lhs_info.elem_type.fmt(sema.mod), rhs_ty.fmt(sema.mod),
+
+    const resolved_elem_ty = t: {
+        var trash_block = block.makeSubBlock();
+        trash_block.is_comptime = false;
+        defer trash_block.instructions.deinit(sema.gpa);
+
+        const instructions = [_]Air.Inst.Ref{
+            try trash_block.addBitCast(lhs_info.elem_type, .void_value),
+            try trash_block.addBitCast(rhs_info.elem_type, .void_value),
+        };
+        break :t try sema.resolvePeerTypes(block, src, &instructions, .{
+            .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
         });
-    }
+    };
 
-    // When there is a sentinel mismatch, no sentinel on the result. The type system
-    // will catch this if it is a problem.
-    var res_sent: ?Value = null;
-    if (rhs_info.sentinel != null and lhs_info.sentinel != null) {
-        if (rhs_info.sentinel.?.eql(lhs_info.sentinel.?, lhs_info.elem_type, sema.mod)) {
-            res_sent = lhs_info.sentinel.?;
+    // When there is a sentinel mismatch, no sentinel on the result.
+    // Otherwise, use the sentinel value provided by either operand,
+    // coercing it to the peer-resolved element type.
+    const res_sent_val: ?Value = s: {
+        if (lhs_info.sentinel) |lhs_sent_val| {
+            const lhs_sent = try sema.addConstant(lhs_info.elem_type, lhs_sent_val);
+            if (rhs_info.sentinel) |rhs_sent_val| {
+                const rhs_sent = try sema.addConstant(rhs_info.elem_type, rhs_sent_val);
+                const lhs_sent_casted = try sema.coerce(block, resolved_elem_ty, lhs_sent, lhs_src);
+                const rhs_sent_casted = try sema.coerce(block, resolved_elem_ty, rhs_sent, rhs_src);
+                const lhs_sent_casted_val = try sema.resolveConstValue(block, lhs_src, lhs_sent_casted);
+                const rhs_sent_casted_val = try sema.resolveConstValue(block, rhs_src, rhs_sent_casted);
+                if (try sema.valuesEqual(block, src, lhs_sent_casted_val, rhs_sent_casted_val, resolved_elem_ty)) {
+                    break :s lhs_sent_casted_val;
+                } else {
+                    break :s null;
+                }
+            } else {
+                const lhs_sent_casted = try sema.coerce(block, resolved_elem_ty, lhs_sent, lhs_src);
+                const lhs_sent_casted_val = try sema.resolveConstValue(block, lhs_src, lhs_sent_casted);
+                break :s lhs_sent_casted_val;
+            }
+        } else {
+            if (rhs_info.sentinel) |rhs_sent_val| {
+                const rhs_sent = try sema.addConstant(rhs_info.elem_type, rhs_sent_val);
+                const rhs_sent_casted = try sema.coerce(block, resolved_elem_ty, rhs_sent, rhs_src);
+                const rhs_sent_casted_val = try sema.resolveConstValue(block, rhs_src, rhs_sent_casted);
+                break :s rhs_sent_casted_val;
+            } else {
+                break :s null;
+            }
         }
-    }
+    };
 
-    if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
+    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_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 runtime_src = if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| rs: {
         if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| {
-            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 final_len = lhs_len + rhs_len;
-            const final_len_including_sent = final_len + @boolToInt(res_sent != null);
-            const lhs_single_ptr = lhs_ty.isSinglePointer();
-            const rhs_single_ptr = rhs_ty.isSinglePointer();
-            const lhs_sub_val = if (lhs_single_ptr) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val;
-            const rhs_sub_val = if (rhs_single_ptr) (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).? else rhs_val;
-            var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded);
-            defer anon_decl.deinit();
+            const lhs_sub_val = if (lhs_ty.isSinglePointer())
+                (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).?
+            else
+                lhs_val;
 
-            const buf = try anon_decl.arena().alloc(Value, final_len_including_sent);
-            {
-                var i: usize = 0;
-                while (i < lhs_len) : (i += 1) {
-                    const val = try lhs_sub_val.elemValue(sema.mod, sema.arena, i);
-                    buf[i] = try val.copy(anon_decl.arena());
-                }
+            const rhs_sub_val = if (rhs_ty.isSinglePointer())
+                (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).?
+            else
+                rhs_val;
+
+            const final_len_including_sent = result_len + @boolToInt(res_sent_val != null);
+            const element_vals = try sema.arena.alloc(Value, final_len_including_sent);
+            var elem_i: usize = 0;
+            while (elem_i < lhs_len) : (elem_i += 1) {
+                element_vals[elem_i] = try lhs_sub_val.elemValue(sema.mod, sema.arena, elem_i);
             }
-            {
-                var i: usize = 0;
-                while (i < rhs_len) : (i += 1) {
-                    const val = try rhs_sub_val.elemValue(sema.mod, sema.arena, i);
-                    buf[lhs_len + i] = try val.copy(anon_decl.arena());
-                }
+            while (elem_i < result_len) : (elem_i += 1) {
+                element_vals[elem_i] = try rhs_sub_val.elemValue(sema.mod, sema.arena, elem_i - lhs_len);
             }
-            const ty = if (res_sent) |rs| ty: {
-                buf[final_len] = try rs.copy(anon_decl.arena());
-                break :ty try Type.Tag.array_sentinel.create(anon_decl.arena(), .{
-                    .len = final_len,
-                    .elem_type = try lhs_info.elem_type.copy(anon_decl.arena()),
-                    .sentinel = try rs.copy(anon_decl.arena()),
-                });
-            } else try Type.Tag.array.create(anon_decl.arena(), .{
-                .len = final_len,
-                .elem_type = try lhs_info.elem_type.copy(anon_decl.arena()),
-            });
-            const val = try Value.Tag.aggregate.create(anon_decl.arena(), buf);
-            const decl = try anon_decl.finish(ty, val, 0);
-            if (lhs_ty.zigTypeTag() == .Pointer or rhs_ty.zigTypeTag() == .Pointer) {
-                return sema.analyzeDeclRef(decl);
-            } else {
-                return sema.analyzeDeclVal(block, .unneeded, decl);
+            if (res_sent_val) |sent_val| {
+                element_vals[result_len] = sent_val;
             }
-        } else {
-            return sema.fail(block, lhs_src, "TODO runtime array_cat", .{});
+            const val = try Value.Tag.aggregate.create(sema.arena, element_vals);
+            return sema.addConstantMaybeRef(block, src, result_ty, val, is_ref);
+        } else break :rs rhs_src;
+    } else lhs_src;
+
+    try sema.requireRuntimeBlock(block, runtime_src);
+
+    if (is_ref) {
+        const target = sema.mod.getTarget();
+        const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{
+            .pointee_type = result_ty,
+            .@"addrspace" = target_util.defaultAddressSpace(target, .local),
+        });
+        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),
+        });
+
+        var elem_i: usize = 0;
+        while (elem_i < lhs_len) : (elem_i += 1) {
+            const elem_index = try sema.addIntUnsigned(Type.usize, elem_i);
+            const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty);
+            const init = try sema.elemVal(block, lhs_src, lhs, elem_index, src);
+            try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store);
         }
-    } else {
-        return sema.fail(block, lhs_src, "TODO runtime array_cat", .{});
+        while (elem_i < result_len) : (elem_i += 1) {
+            const elem_index = try sema.addIntUnsigned(Type.usize, elem_i);
+            const rhs_index = try sema.addIntUnsigned(Type.usize, elem_i - lhs_len);
+            const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty);
+            const init = try sema.elemVal(block, rhs_src, rhs, rhs_index, src);
+            try sema.storePtr2(block, src, elem_ptr, src, init, rhs_src, .store);
+        }
+
+        return alloc;
     }
+
+    const element_refs = try sema.arena.alloc(Air.Inst.Ref, result_len);
+    {
+        var elem_i: usize = 0;
+        while (elem_i < lhs_len) : (elem_i += 1) {
+            const index = try sema.addIntUnsigned(Type.usize, elem_i);
+            const init = try sema.elemVal(block, lhs_src, lhs, index, src);
+            element_refs[elem_i] = try sema.coerce(block, resolved_elem_ty, init, lhs_src);
+        }
+        while (elem_i < result_len) : (elem_i += 1) {
+            const index = try sema.addIntUnsigned(Type.usize, elem_i - lhs_len);
+            const init = try sema.elemVal(block, rhs_src, rhs, index, src);
+            element_refs[elem_i] = try sema.coerce(block, resolved_elem_ty, init, rhs_src);
+        }
+    }
+
+    return block.addAggregateInit(result_ty, element_refs);
 }
 
 fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, inst: Air.Inst.Ref) !?Type.ArrayInfo {
@@ -24947,7 +25013,7 @@ fn valuesEqual(
 }
 
 /// Asserts the values are comparable vectors of type `ty`.
-pub fn compareVector(
+fn compareVector(
     sema: *Sema,
     block: *Block,
     src: LazySrcLoc,
test/behavior/eval.zig
@@ -733,17 +733,49 @@ test "*align(1) u16 is the same as *align(1:0:2) u16" {
     }
 }
 
-test "array concatenation forces comptime" {
-    if (builtin.zig_backend != .stage1) {
-        // note: our plan is to change the language to support runtime array
-        // concatenation instead of making this test pass.
-        return error.SkipZigTest; // TODO
-    }
+test "array concatenation 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) ++ oneItem(4);
     try expect(std.mem.eql(i32, &a, &[_]i32{ 3, 4 }));
 }
 
+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;
+    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]u8{ 200, 225, 255 };
+    var c = a ++ b;
+    try expect(@TypeOf(c) == [5]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);
+}
+
+test "array concatenation peer resolves element types - 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]u8{ 200, 225, 255 };
+    var c = &a ++ &b;
+    try expect(@TypeOf(c) == *[5]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);
+}
+
 test "array multiplication forces comptime" {
     if (builtin.zig_backend != .stage1) {
         // note: our plan is to change the language to support runtime array