Commit df7d6d263e

Andrew Kelley <andrew@ziglang.org>
2021-10-14 02:53:28
stage2: implement opaque declarations
* Module: implement opaque type namespace lookup * Add `Type.type` for convenience * Sema: fix `validateVarType` for pointer-to-opaque * x86_64 ABI: implement support for pointers * LLVM backend: fix lowering of opaque types * Type: implement equality checking for opaques
1 parent da7fcfd
Changed files (8)
src/arch/x86_64/abi.zig
@@ -34,6 +34,17 @@ pub fn classifySystemV(ty: Type, target: Target) [8]Class {
     };
     var result = [1]Class{.none} ** 8;
     switch (ty.zigTypeTag()) {
+        .Pointer => switch (ty.ptrSize()) {
+            .Slice => {
+                result[0] = .integer;
+                result[1] = .integer;
+                return result;
+            },
+            else => {
+                result[0] = .integer;
+                return result;
+            },
+        },
         .Int, .Enum, .ErrorSet => {
             const bits = ty.intInfo(target).bits;
             if (bits <= 64) {
src/codegen/llvm.zig
@@ -758,11 +758,27 @@ pub const DeclGen = struct {
                     };
                     return dg.context.structType(&fields, fields.len, .False);
                 } else {
-                    const elem_type = try dg.llvmType(t.elemType());
                     const llvm_addrspace = dg.llvmAddressSpace(t.ptrAddressSpace());
-                    return elem_type.pointerType(llvm_addrspace);
+                    const llvm_elem_ty = try dg.llvmType(t.childType());
+                    return llvm_elem_ty.pointerType(llvm_addrspace);
                 }
             },
+            .Opaque => {
+                const gop = try dg.object.type_map.getOrPut(gpa, t);
+                if (gop.found_existing) return gop.value_ptr.*;
+
+                // The Type memory is ephemeral; since we want to store a longer-lived
+                // reference, we need to copy it here.
+                gop.key_ptr.* = try t.copy(&dg.object.type_map_arena.allocator);
+
+                const opaque_obj = t.castTag(.@"opaque").?.data;
+                const name = try opaque_obj.getFullyQualifiedName(gpa);
+                defer gpa.free(name);
+
+                const llvm_struct_ty = dg.context.structCreateNamed(name);
+                gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
+                return llvm_struct_ty;
+            },
             .Array => {
                 const elem_type = try dg.llvmType(t.elemType());
                 const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null);
@@ -896,7 +912,6 @@ pub const DeclGen = struct {
 
             .BoundFn => @panic("TODO remove BoundFn from the language"),
 
-            .Opaque,
             .Frame,
             .AnyFrame,
             .Vector,
src/Module.zig
@@ -708,7 +708,9 @@ pub const Decl = struct {
                 return ty.castTag(.empty_struct).?.data;
             },
             .@"opaque" => {
-                @panic("TODO opaque types");
+                const opaque_obj = ty.cast(Type.Payload.Opaque).?.data;
+                assert(opaque_obj.owner_decl == decl);
+                return &opaque_obj.namespace;
             },
             .@"union", .union_tagged => {
                 const union_obj = ty.cast(Type.Payload.Union).?.data;
@@ -1080,6 +1082,27 @@ pub const Union = struct {
     }
 };
 
+pub const Opaque = struct {
+    /// The Decl that corresponds to the opaque itself.
+    owner_decl: *Decl,
+    /// Represents the declarations inside this opaque.
+    namespace: Namespace,
+    /// Offset from `owner_decl`, points to the opaque decl AST node.
+    node_offset: i32,
+
+    pub fn srcLoc(self: Opaque) SrcLoc {
+        return .{
+            .file_scope = self.owner_decl.getFileScope(),
+            .parent_decl_node = self.owner_decl.src_node,
+            .lazy = .{ .node_offset = self.node_offset },
+        };
+    }
+
+    pub fn getFullyQualifiedName(s: *Opaque, gpa: *Allocator) ![:0]u8 {
+        return s.owner_decl.getFullyQualifiedName(gpa);
+    }
+};
+
 /// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
 /// Extern functions do not have this data structure; they are represented by
 /// the `Decl` only, with a `Value` tag of `extern_fn`.
src/Sema.zig
@@ -957,7 +957,7 @@ fn zirExtended(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         .struct_decl        => return sema.zirStructDecl(        block, extended, inst),
         .enum_decl          => return sema.zirEnumDecl(          block, extended),
         .union_decl         => return sema.zirUnionDecl(         block, extended, inst),
-        .opaque_decl        => return sema.zirOpaqueDecl(        block, extended, inst),
+        .opaque_decl        => return sema.zirOpaqueDecl(        block, extended),
         .ret_ptr            => return sema.zirRetPtr(            block, extended),
         .ret_type           => return sema.zirRetType(           block, extended),
         .this               => return sema.zirThis(              block, extended),
@@ -1432,7 +1432,7 @@ fn zirStructDecl(
     const struct_val = try Value.Tag.ty.create(&new_decl_arena.allocator, struct_ty);
     const type_name = try sema.createTypeName(block, small.name_strategy);
     const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
-        .ty = Type.initTag(.type),
+        .ty = Type.type,
         .val = struct_val,
     }, type_name);
     new_decl.owns_tv = true;
@@ -1541,7 +1541,7 @@ fn zirEnumDecl(
     const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty);
     const type_name = try sema.createTypeName(block, small.name_strategy);
     const new_decl = try mod.createAnonymousDeclNamed(block, .{
-        .ty = Type.initTag(.type),
+        .ty = Type.type,
         .val = enum_val,
     }, type_name);
     new_decl.owns_tv = true;
@@ -1731,7 +1731,7 @@ fn zirUnionDecl(
     const union_val = try Value.Tag.ty.create(&new_decl_arena.allocator, union_ty);
     const type_name = try sema.createTypeName(block, small.name_strategy);
     const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
-        .ty = Type.initTag(.type),
+        .ty = Type.type,
         .val = union_val,
     }, type_name);
     new_decl.owns_tv = true;
@@ -1764,14 +1764,63 @@ fn zirOpaqueDecl(
     sema: *Sema,
     block: *Block,
     extended: Zir.Inst.Extended.InstData,
-    inst: Zir.Inst.Index,
 ) CompileError!Air.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
 
-    _ = extended;
-    _ = inst;
-    return sema.fail(block, sema.src, "TODO implement zirOpaqueDecl", .{});
+    const mod = sema.mod;
+    const gpa = sema.gpa;
+    const small = @bitCast(Zir.Inst.OpaqueDecl.Small, extended.small);
+    var extra_index: usize = extended.operand;
+
+    const src: LazySrcLoc = if (small.has_src_node) blk: {
+        const node_offset = @bitCast(i32, sema.code.extra[extra_index]);
+        extra_index += 1;
+        break :blk .{ .node_offset = node_offset };
+    } else sema.src;
+
+    const decls_len = if (small.has_decls_len) blk: {
+        const decls_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        break :blk decls_len;
+    } else 0;
+
+    var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
+    errdefer new_decl_arena.deinit();
+
+    const opaque_obj = try new_decl_arena.allocator.create(Module.Opaque);
+    const opaque_ty_payload = try new_decl_arena.allocator.create(Type.Payload.Opaque);
+    opaque_ty_payload.* = .{
+        .base = .{ .tag = .@"opaque" },
+        .data = opaque_obj,
+    };
+    const opaque_ty = Type.initPayload(&opaque_ty_payload.base);
+    const opaque_val = try Value.Tag.ty.create(&new_decl_arena.allocator, opaque_ty);
+    const type_name = try sema.createTypeName(block, small.name_strategy);
+    const new_decl = try mod.createAnonymousDeclNamed(block, .{
+        .ty = Type.type,
+        .val = opaque_val,
+    }, type_name);
+    new_decl.owns_tv = true;
+    errdefer mod.abortAnonDecl(new_decl);
+
+    opaque_obj.* = .{
+        .owner_decl = new_decl,
+        .node_offset = src.node_offset,
+        .namespace = .{
+            .parent = block.namespace,
+            .ty = opaque_ty,
+            .file_scope = block.getFileScope(),
+        },
+    };
+    std.log.scoped(.module).debug("create opaque {*} owned by {*} ({s})", .{
+        &opaque_obj.namespace, new_decl, new_decl.name,
+    });
+
+    extra_index = try mod.scanNamespace(&opaque_obj.namespace, extra_index, decls_len, new_decl);
+
+    try new_decl.finalizeNewArena(&new_decl_arena);
+    return sema.analyzeDeclVal(block, src, new_decl);
 }
 
 fn zirErrorSetDecl(
@@ -1797,7 +1846,7 @@ fn zirErrorSetDecl(
     const error_set_val = try Value.Tag.ty.create(&new_decl_arena.allocator, error_set_ty);
     const type_name = try sema.createTypeName(block, name_strategy);
     const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
-        .ty = Type.initTag(.type),
+        .ty = Type.type,
         .val = error_set_val,
     }, type_name);
     new_decl.owns_tv = true;
@@ -4278,7 +4327,7 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
         .names_len = @intCast(u32, new_names.len),
     };
     const error_set_ty = try Type.Tag.error_set.create(sema.arena, new_error_set);
-    return sema.addConstant(Type.initTag(.type), try Value.Tag.ty.create(sema.arena, error_set_ty));
+    return sema.addConstant(Type.type, try Value.Tag.ty.create(sema.arena, error_set_ty));
 }
 
 fn zirEnumLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -10158,6 +10207,11 @@ fn validateVarType(
         .Null,
         => break false,
 
+        .Pointer => {
+            const elem_ty = ty.childType();
+            if (elem_ty.zigTypeTag() == .Opaque) return;
+            ty = elem_ty;
+        },
         .Opaque => break is_extern,
 
         .Optional => {
@@ -10165,7 +10219,8 @@ fn validateVarType(
             const child_ty = ty.optionalChild(&buf);
             return validateVarType(sema, block, src, child_ty, is_extern);
         },
-        .Pointer, .Array, .Vector => ty = ty.elemType(),
+        .Array, .Vector => ty = ty.elemType(),
+
         .ErrorUnion => ty = ty.errorUnionPayload(),
 
         .Fn => @panic("TODO fn validateVarType"),
@@ -12978,7 +13033,7 @@ fn generateUnionTagTypeNumbered(
     const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty);
     // TODO better type name
     const new_decl = try mod.createAnonymousDecl(block, .{
-        .ty = Type.initTag(.type),
+        .ty = Type.type,
         .val = enum_val,
     });
     new_decl.owns_tv = true;
@@ -13014,7 +13069,7 @@ fn generateUnionTagTypeSimple(sema: *Sema, block: *Block, fields_len: u32) !Type
     const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty);
     // TODO better type name
     const new_decl = try mod.createAnonymousDecl(block, .{
-        .ty = Type.initTag(.type),
+        .ty = Type.type,
         .val = enum_val,
     });
     new_decl.owns_tv = true;
src/type.zig
@@ -571,6 +571,11 @@ pub const Type = extern union {
                 }
                 return a.tag() == b.tag();
             },
+            .Opaque => {
+                const opaque_obj_a = a.castTag(.@"opaque").?.data;
+                const opaque_obj_b = b.castTag(.@"opaque").?.data;
+                return opaque_obj_a == opaque_obj_b;
+            },
             .Union => {
                 if (a.cast(Payload.Union)) |a_payload| {
                     if (b.cast(Payload.Union)) |b_payload| {
@@ -611,7 +616,6 @@ pub const Type = extern union {
                 return false;
             },
             .Float => return a.tag() == b.tag(),
-            .Opaque,
             .BoundFn,
             .Frame,
             => std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }),
@@ -1408,6 +1412,7 @@ pub const Type = extern union {
             .extern_options,
             .@"anyframe",
             .anyframe_T,
+            .@"opaque",
             => true,
 
             .function => !self.castTag(.function).?.data.is_generic,
@@ -1499,7 +1504,6 @@ pub const Type = extern union {
             .enum_literal,
             .empty_struct,
             .empty_struct_literal,
-            .@"opaque",
             .type_info,
             .bound_fn,
             => false,
@@ -3097,7 +3101,7 @@ pub const Type = extern union {
             .enum_full => &self.castTag(.enum_full).?.data.namespace,
             .enum_nonexhaustive => &self.castTag(.enum_nonexhaustive).?.data.namespace,
             .empty_struct => self.castTag(.empty_struct).?.data,
-            .@"opaque" => &self.castTag(.@"opaque").?.data,
+            .@"opaque" => &self.castTag(.@"opaque").?.data.namespace,
             .@"union" => &self.castTag(.@"union").?.data.namespace,
             .union_tagged => &self.castTag(.union_tagged).?.data.namespace,
 
@@ -3870,7 +3874,7 @@ pub const Type = extern union {
 
         pub const Opaque = struct {
             base: Payload = .{ .tag = .@"opaque" },
-            data: Module.Namespace,
+            data: *Module.Opaque,
         };
 
         pub const Struct = struct {
@@ -3904,6 +3908,7 @@ pub const Type = extern union {
     pub const @"usize" = initTag(.usize);
     pub const @"comptime_int" = initTag(.comptime_int);
     pub const @"void" = initTag(.void);
+    pub const @"type" = initTag(.type);
 
     pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type {
         assert(d.host_size == 0 or d.bit_offset < d.host_size * 8);
test/behavior/basic.zig
@@ -188,3 +188,15 @@ fn testMemcpyMemset() !void {
     try expect(bar[11] == 'A');
     try expect(bar[19] == 'A');
 }
+
+const OpaqueA = opaque {};
+const OpaqueB = opaque {};
+
+test "variable is allowed to be a pointer to an opaque type" {
+    var x: i32 = 1234;
+    _ = hereIsAnOpaqueType(@ptrCast(*OpaqueA, &x));
+}
+fn hereIsAnOpaqueType(ptr: *OpaqueA) *OpaqueA {
+    var a = ptr;
+    return a;
+}
test/behavior/misc.zig
@@ -5,22 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings;
 const mem = std.mem;
 const builtin = @import("builtin");
 
-test "slicing" {
-    var array: [20]i32 = undefined;
-
-    array[5] = 1234;
-
-    var slice = array[5..10];
-
-    if (slice.len != 5) unreachable;
-
-    const ptr = &slice[0];
-    if (ptr.* != 1234) unreachable;
-
-    var slice_rest = array[10..];
-    if (slice_rest.len != 10) unreachable;
-}
-
 test "constant equal function pointers" {
     const alias = emptyFn;
     try expect(comptime x: {
@@ -230,15 +214,6 @@ test "opaque types" {
     try expect(mem.eql(u8, @typeName(OpaqueB), "OpaqueB"));
 }
 
-test "variable is allowed to be a pointer to an opaque type" {
-    var x: i32 = 1234;
-    _ = hereIsAnOpaqueType(@ptrCast(*OpaqueA, &x));
-}
-fn hereIsAnOpaqueType(ptr: *OpaqueA) *OpaqueA {
-    var a = ptr;
-    return a;
-}
-
 test "comptime if inside runtime while which unconditionally breaks" {
     testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true);
     comptime testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true);
test/behavior/slice_stage1.zig
@@ -4,6 +4,22 @@ const expectEqualSlices = std.testing.expectEqualSlices;
 const expectEqual = std.testing.expectEqual;
 const mem = std.mem;
 
+test "slicing" {
+    var array: [20]i32 = undefined;
+
+    array[5] = 1234;
+
+    var slice = array[5..10];
+
+    if (slice.len != 5) unreachable;
+
+    const ptr = &slice[0];
+    if (ptr.* != 1234) unreachable;
+
+    var slice_rest = array[10..];
+    if (slice_rest.len != 10) unreachable;
+}
+
 const x = @intToPtr([*]i32, 0x1000)[0..0x500];
 const y = x[0x100..];
 test "compile time slice of pointer to hard coded address" {