Commit dfeffcfbf8

Veikka Tuominen <git@vexu.eu>
2022-02-28 09:09:23
stage2: tuple mul/cat
1 parent 3a65fa2
Changed files (4)
src/Sema.zig
@@ -8059,6 +8059,78 @@ fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
     return block.addTyOp(.not, operand_type, operand);
 }
 
+fn analyzeTupleCat(
+    sema: *Sema,
+    block: *Block,
+    src_node: i32,
+    lhs: Air.Inst.Ref,
+    rhs: Air.Inst.Ref,
+) CompileError!Air.Inst.Ref {
+    const lhs_ty = sema.typeOf(lhs);
+    const rhs_ty = sema.typeOf(rhs);
+    const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node };
+    const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node };
+
+    const lhs_tuple = lhs_ty.tupleFields();
+    const rhs_tuple = rhs_ty.tupleFields();
+    const dest_fields = lhs_tuple.types.len + rhs_tuple.types.len;
+
+    if (dest_fields == 0) {
+        return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value));
+    }
+
+    const types = try sema.arena.alloc(Type, dest_fields);
+    const values = try sema.arena.alloc(Value, dest_fields);
+
+    const opt_runtime_src = rs: {
+        var runtime_src: ?LazySrcLoc = null;
+        for (lhs_tuple.types) |ty, i| {
+            types[i] = ty;
+            values[i] = lhs_tuple.values[i];
+            const operand_src = lhs_src; // TODO better source location
+            if (values[i].tag() == .unreachable_value) {
+                runtime_src = operand_src;
+            }
+        }
+        const offset = lhs_tuple.types.len;
+        for (rhs_tuple.types) |ty, i| {
+            types[i + offset] = ty;
+            values[i + offset] = rhs_tuple.values[i];
+            const operand_src = rhs_src; // TODO better source location
+            if (rhs_tuple.values[i].tag() == .unreachable_value) {
+                runtime_src = operand_src;
+            }
+        }
+        break :rs runtime_src;
+    };
+
+    const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{
+        .types = types,
+        .values = values,
+    });
+
+    const runtime_src = opt_runtime_src orelse {
+        const tuple_val = try Value.Tag.@"struct".create(sema.arena, values);
+        return sema.addConstant(tuple_ty, tuple_val);
+    };
+
+    try sema.requireRuntimeBlock(block, runtime_src);
+
+    const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_fields);
+    for (lhs_tuple.types) |_, i| {
+        const operand_src = lhs_src; // TODO better source location
+        element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, lhs, @intCast(u32, i), lhs_ty);
+    }
+    const offset = lhs_tuple.types.len;
+    for (rhs_tuple.types) |_, i| {
+        const operand_src = rhs_src; // TODO better source location
+        element_refs[i + offset] =
+            try sema.tupleFieldValByIndex(block, operand_src, rhs, @intCast(u32, i), rhs_ty);
+    }
+
+    return block.addAggregateInit(tuple_ty, element_refs);
+}
+
 fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
@@ -8069,6 +8141,11 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const rhs = sema.resolveInst(extra.rhs);
     const lhs_ty = sema.typeOf(lhs);
     const rhs_ty = sema.typeOf(rhs);
+
+    if (lhs_ty.isTuple() and rhs_ty.isTuple()) {
+        return sema.analyzeTupleCat(block, inst_data.src_node, lhs, rhs);
+    }
+
     const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
     const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
 
@@ -8165,6 +8242,72 @@ fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, inst: Air.Inst.R
     };
 }
 
+fn analyzeTupleMul(
+    sema: *Sema,
+    block: *Block,
+    src_node: i32,
+    operand: Air.Inst.Ref,
+    factor: u64,
+) CompileError!Air.Inst.Ref {
+    const operand_ty = sema.typeOf(operand);
+    const operand_tuple = operand_ty.tupleFields();
+    const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node };
+    const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node };
+
+    const tuple_len = operand_tuple.types.len;
+    const final_len_u64 = std.math.mul(u64, tuple_len, factor) catch
+        return sema.fail(block, rhs_src, "operation results in overflow", .{});
+
+    if (final_len_u64 == 0) {
+        return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value));
+    }
+
+    const types = try sema.arena.alloc(Type, final_len_u64);
+    const values = try sema.arena.alloc(Value, final_len_u64);
+
+    const opt_runtime_src = rs: {
+        var runtime_src: ?LazySrcLoc = null;
+        for (operand_tuple.types) |ty, i| {
+            types[i] = ty;
+            values[i] = operand_tuple.values[i];
+            const operand_src = lhs_src; // TODO better source location
+            if (values[i].tag() == .unreachable_value) {
+                runtime_src = operand_src;
+            }
+        }
+        var i: usize = 1;
+        while (i < factor) : (i += 1) {
+            mem.copy(Type, types[tuple_len * i ..], operand_tuple.types);
+            mem.copy(Value, values[tuple_len * i ..], operand_tuple.values);
+        }
+        break :rs runtime_src;
+    };
+
+    const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{
+        .types = types,
+        .values = values,
+    });
+
+    const runtime_src = opt_runtime_src orelse {
+        const tuple_val = try Value.Tag.@"struct".create(sema.arena, values);
+        return sema.addConstant(tuple_ty, tuple_val);
+    };
+
+    try sema.requireRuntimeBlock(block, runtime_src);
+
+    const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len_u64);
+    for (operand_tuple.types) |_, i| {
+        const operand_src = lhs_src; // TODO better source location
+        element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, operand, @intCast(u32, i), operand_ty);
+    }
+    var i: usize = 1;
+    while (i < factor) : (i += 1) {
+        mem.copy(Air.Inst.Ref, element_refs[tuple_len * i ..], element_refs[0..tuple_len]);
+    }
+
+    return block.addAggregateInit(tuple_ty, element_refs);
+}
+
 fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
@@ -8179,6 +8322,11 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
     // In `**` rhs has to be comptime-known, but lhs can be runtime-known
     const factor = try sema.resolveInt(block, rhs_src, extra.rhs, Type.usize);
+
+    if (lhs_ty.isTuple()) {
+        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});
 
@@ -14754,6 +14902,11 @@ fn tupleFieldVal(
             tuple_ty, field_name, @errorName(err),
         });
     };
+    if (field_index >= tuple_ty.structFieldCount()) {
+        return sema.fail(block, field_name_src, "tuple {} has no such field '{s}'", .{
+            tuple_ty, field_name,
+        });
+    }
     return tupleFieldValByIndex(sema, block, src, tuple_byval, field_index, tuple_ty);
 }
 
src/type.zig
@@ -4531,7 +4531,15 @@ pub const Type = extern union {
     };
 
     pub fn isTuple(ty: Type) bool {
-        return ty.tag() == .tuple;
+        return ty.tag() == .tuple or ty.tag() == .empty_struct_literal;
+    }
+
+    pub fn tupleFields(ty: Type) Payload.Tuple.Data {
+        return switch (ty.tag()) {
+            .tuple => ty.castTag(.tuple).?.data,
+            .empty_struct_literal => .{ .types = &.{}, .values = &.{} },
+            else => unreachable,
+        };
     }
 
     /// The sub-types are named after what fields they contain.
@@ -4683,11 +4691,13 @@ pub const Type = extern union {
 
         pub const Tuple = struct {
             base: Payload = .{ .tag = .tuple },
-            data: struct {
+            data: Data,
+
+            pub const Data = struct {
                 types: []Type,
                 /// unreachable_value elements are used to indicate runtime-known.
                 values: []Value,
-            },
+            };
         };
 
         pub const Union = struct {
src/value.zig
@@ -1829,7 +1829,7 @@ pub const Value = extern union {
         assert(a_tag != .undef);
         assert(b_tag != .undef);
         if (a_tag == b_tag) switch (a_tag) {
-            .void_value, .null_value, .the_only_possible_value => return true,
+            .void_value, .null_value, .the_only_possible_value, .empty_struct_value => return true,
             .enum_literal => {
                 const a_name = a.castTag(.enum_literal).?.data;
                 const b_name = b.castTag(.enum_literal).?.data;
@@ -1892,10 +1892,18 @@ pub const Value = extern union {
                 return a_payload == b_payload;
             },
             .@"struct" => {
-                const fields = ty.structFields().values();
                 const a_field_vals = a.castTag(.@"struct").?.data;
                 const b_field_vals = b.castTag(.@"struct").?.data;
                 assert(a_field_vals.len == b_field_vals.len);
+                if (ty.isTuple()) {
+                    const types = ty.tupleFields().types;
+                    assert(types.len == a_field_vals.len);
+                    for (types) |field_ty, i| {
+                        if (!eql(a_field_vals[i], b_field_vals[i], field_ty)) return false;
+                    }
+                    return true;
+                }
+                const fields = ty.structFields().values();
                 assert(fields.len == a_field_vals.len);
                 for (fields) |field, i| {
                     if (!eql(a_field_vals[i], b_field_vals[i], field.ty)) return false;
@@ -1967,11 +1975,10 @@ pub const Value = extern union {
                 return true;
             },
             .Struct => {
-                // must be a struct with no fields since we checked for if
-                // both have the struct tag above.
-                const fields = ty.structFields().values();
-                assert(fields.len == 0);
-                return true;
+                // A tuple can be represented with .empty_struct_value,
+                // the_one_possible_value, .@"struct" in which case we could
+                // end up here and the values are equal if the type has zero fields.
+                return ty.structFieldCount() != 0;
             },
             else => return order(a, b).compare(.eq),
         }
@@ -2024,6 +2031,13 @@ pub const Value = extern union {
                 }
             },
             .Struct => {
+                if (ty.isTuple()) {
+                    const fields = ty.tupleFields();
+                    for (fields.values) |field_val, i| {
+                        field_val.hash(fields.types[i], hasher);
+                    }
+                    return;
+                }
                 const fields = ty.structFields().values();
                 if (fields.len == 0) return;
                 const field_values = val.castTag(.@"struct").?.data;
test/behavior/tuple.zig
@@ -23,28 +23,30 @@ test "tuple concatenation" {
 }
 
 test "tuple multiplication" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const S = struct {
         fn doTheTest() !void {
             {
                 const t = .{} ** 4;
-                try expectEqual(0, @typeInfo(@TypeOf(t)).Struct.fields.len);
+                try expect(@typeInfo(@TypeOf(t)).Struct.fields.len == 0);
             }
             {
                 const t = .{'a'} ** 4;
-                try expectEqual(4, @typeInfo(@TypeOf(t)).Struct.fields.len);
-                inline for (t) |x| try expectEqual('a', x);
+                try expect(@typeInfo(@TypeOf(t)).Struct.fields.len == 4);
+                inline for (t) |x| try expect(x == 'a');
             }
             {
                 const t = .{ 1, 2, 3 } ** 4;
-                try expectEqual(12, @typeInfo(@TypeOf(t)).Struct.fields.len);
-                inline for (t) |x, i| try expectEqual(1 + i % 3, x);
+                try expect(@typeInfo(@TypeOf(t)).Struct.fields.len == 12);
+                inline for (t) |x, i| try expect(x == 1 + i % 3);
             }
         }
     };
     try S.doTheTest();
     comptime try S.doTheTest();
+}
+
+test "tuple concatenation" {
+    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
 
     const T = struct {
         fn consume_tuple(tuple: anytype, len: usize) !void {
@@ -86,8 +88,6 @@ test "tuple multiplication" {
 }
 
 test "pass tuple to comptime var parameter" {
-    if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
-
     const S = struct {
         fn Foo(comptime args: anytype) !void {
             try expect(args[0] == 1);