Commit 14cef9dd3d

Andrew Kelley <andrew@ziglang.org>
2020-07-14 05:13:51
stage2 parser: split out PrefixOp into separate AST Nodes
This is part of a larger effort to improve the memory layout of AST nodes of the self-hosted parser to reduce wasted memory. Reduction of wasted memory also translates to improved performance because of fewer memory allocations, and fewer cache misses. Compared to master, when running `zig fmt` on the std lib: * cache-misses: 801,829 => 768,624 * instructions: 3,234,877,167 => 3,232,075,022 * peak memory: 81480 KB => 75964 KB
1 parent 204f61d
Changed files (5)
lib/std/zig/ast.zig
@@ -410,7 +410,20 @@ pub const Node = struct {
 
         // Operators
         InfixOp,
-        PrefixOp,
+        AddressOf,
+        Await,
+        BitNot,
+        BoolNot,
+        OptionalType,
+        Negation,
+        NegationWrap,
+        Resume,
+        Try,
+        ArrayType,
+        /// ArrayType but has a sentinel node.
+        ArrayTypeSentinel,
+        PtrType,
+        SliceType,
         /// Not all suffix operations are under this tag. To save memory, some
         /// suffix operations have dedicated Node tags.
         SuffixOp,
@@ -1797,85 +1810,116 @@ pub const Node = struct {
         }
     };
 
-    pub const PrefixOp = struct {
-        base: Node = Node{ .id = .PrefixOp },
+    pub const AddressOf = SimplePrefixOp(.AddressOf);
+    pub const Await = SimplePrefixOp(.Await);
+    pub const BitNot = SimplePrefixOp(.BitNot);
+    pub const BoolNot = SimplePrefixOp(.BoolNot);
+    pub const OptionalType = SimplePrefixOp(.OptionalType);
+    pub const Negation = SimplePrefixOp(.Negation);
+    pub const NegationWrap = SimplePrefixOp(.NegationWrap);
+    pub const Resume = SimplePrefixOp(.Resume);
+    pub const Try = SimplePrefixOp(.Try);
+
+    pub fn SimplePrefixOp(comptime tag: Id) type {
+        return struct {
+            base: Node = Node{ .id = tag },
+            op_token: TokenIndex,
+            rhs: *Node,
+
+            const Self = @This();
+
+            pub fn iterate(self: *const Self, index: usize) ?*Node {
+                if (index == 0) return self.rhs;
+                return null;
+            }
+
+            pub fn firstToken(self: *const Self) TokenIndex {
+                return self.op_token;
+            }
+
+            pub fn lastToken(self: *const Self) TokenIndex {
+                return self.rhs.lastToken();
+            }
+        };
+    }
+
+    pub const ArrayType = struct {
+        base: Node = Node{ .id = .ArrayType },
         op_token: TokenIndex,
-        op: Op,
         rhs: *Node,
+        len_expr: *Node,
 
-        pub const Op = union(enum) {
-            AddressOf,
-            ArrayType: ArrayInfo,
-            Await,
-            BitNot,
-            BoolNot,
-            OptionalType,
-            Negation,
-            NegationWrap,
-            Resume,
-            PtrType: PtrInfo,
-            SliceType: PtrInfo,
-            Try,
-        };
+        pub fn iterate(self: *const ArrayType, index: usize) ?*Node {
+            var i = index;
 
-        pub const ArrayInfo = struct {
-            len_expr: *Node,
-            sentinel: ?*Node,
-        };
+            if (i < 1) return self.len_expr;
+            i -= 1;
 
-        pub const PtrInfo = struct {
-            allowzero_token: ?TokenIndex = null,
-            align_info: ?Align = null,
-            const_token: ?TokenIndex = null,
-            volatile_token: ?TokenIndex = null,
-            sentinel: ?*Node = null,
-
-            pub const Align = struct {
-                node: *Node,
-                bit_range: ?BitRange,
-
-                pub const BitRange = struct {
-                    start: *Node,
-                    end: *Node,
-                };
-            };
-        };
+            if (i < 1) return self.rhs;
+            i -= 1;
+
+            return null;
+        }
 
-        pub fn iterate(self: *const PrefixOp, index: usize) ?*Node {
+        pub fn firstToken(self: *const ArrayType) TokenIndex {
+            return self.op_token;
+        }
+
+        pub fn lastToken(self: *const ArrayType) TokenIndex {
+            return self.rhs.lastToken();
+        }
+    };
+
+    pub const ArrayTypeSentinel = struct {
+        base: Node = Node{ .id = .ArrayTypeSentinel },
+        op_token: TokenIndex,
+        rhs: *Node,
+        len_expr: *Node,
+        sentinel: *Node,
+
+        pub fn iterate(self: *const ArrayTypeSentinel, index: usize) ?*Node {
             var i = index;
 
-            switch (self.op) {
-                .PtrType, .SliceType => |addr_of_info| {
-                    if (addr_of_info.sentinel) |sentinel| {
-                        if (i < 1) return sentinel;
-                        i -= 1;
-                    }
+            if (i < 1) return self.len_expr;
+            i -= 1;
 
-                    if (addr_of_info.align_info) |align_info| {
-                        if (i < 1) return align_info.node;
-                        i -= 1;
-                    }
-                },
+            if (i < 1) return self.sentinel;
+            i -= 1;
 
-                .ArrayType => |array_info| {
-                    if (i < 1) return array_info.len_expr;
-                    i -= 1;
-                    if (array_info.sentinel) |sentinel| {
-                        if (i < 1) return sentinel;
-                        i -= 1;
-                    }
-                },
+            if (i < 1) return self.rhs;
+            i -= 1;
 
-                .AddressOf,
-                .Await,
-                .BitNot,
-                .BoolNot,
-                .OptionalType,
-                .Negation,
-                .NegationWrap,
-                .Try,
-                .Resume,
-                => {},
+            return null;
+        }
+
+        pub fn firstToken(self: *const ArrayTypeSentinel) TokenIndex {
+            return self.op_token;
+        }
+
+        pub fn lastToken(self: *const ArrayTypeSentinel) TokenIndex {
+            return self.rhs.lastToken();
+        }
+    };
+
+    pub const PtrType = struct {
+        base: Node = Node{ .id = .PtrType },
+        op_token: TokenIndex,
+        rhs: *Node,
+        /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents
+        /// one of these possibly-null things. Then we have them directly follow the PtrType in memory.
+        ptr_info: PtrInfo = .{},
+
+        pub fn iterate(self: *const PtrType, index: usize) ?*Node {
+            var i = index;
+
+            if (self.ptr_info.sentinel) |sentinel| {
+                if (i < 1) return sentinel;
+                i -= 1;
+            }
+
+            if (self.ptr_info.align_info) |align_info| {
+                if (i < 1) return align_info.node;
+                i -= 1;
             }
 
             if (i < 1) return self.rhs;
@@ -1884,11 +1928,47 @@ pub const Node = struct {
             return null;
         }
 
-        pub fn firstToken(self: *const PrefixOp) TokenIndex {
+        pub fn firstToken(self: *const PtrType) TokenIndex {
             return self.op_token;
         }
 
-        pub fn lastToken(self: *const PrefixOp) TokenIndex {
+        pub fn lastToken(self: *const PtrType) TokenIndex {
+            return self.rhs.lastToken();
+        }
+    };
+
+    pub const SliceType = struct {
+        base: Node = Node{ .id = .SliceType },
+        op_token: TokenIndex,
+        rhs: *Node,
+        /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents
+        /// one of these possibly-null things. Then we have them directly follow the SliceType in memory.
+        ptr_info: PtrInfo = .{},
+
+        pub fn iterate(self: *const SliceType, index: usize) ?*Node {
+            var i = index;
+
+            if (self.ptr_info.sentinel) |sentinel| {
+                if (i < 1) return sentinel;
+                i -= 1;
+            }
+
+            if (self.ptr_info.align_info) |align_info| {
+                if (i < 1) return align_info.node;
+                i -= 1;
+            }
+
+            if (i < 1) return self.rhs;
+            i -= 1;
+
+            return null;
+        }
+
+        pub fn firstToken(self: *const SliceType) TokenIndex {
+            return self.op_token;
+        }
+
+        pub fn lastToken(self: *const SliceType) TokenIndex {
             return self.rhs.lastToken();
         }
     };
@@ -2797,6 +2877,24 @@ pub const Node = struct {
     };
 };
 
+pub const PtrInfo = struct {
+    allowzero_token: ?TokenIndex = null,
+    align_info: ?Align = null,
+    const_token: ?TokenIndex = null,
+    volatile_token: ?TokenIndex = null,
+    sentinel: ?*Node = null,
+
+    pub const Align = struct {
+        node: *Node,
+        bit_range: ?BitRange = null,
+
+        pub const BitRange = struct {
+            start: *Node,
+            end: *Node,
+        };
+    };
+};
+
 test "iterate" {
     var root = Node.Root{
         .base = Node{ .id = Node.Id.Root },
lib/std/zig/parse.zig
@@ -1120,10 +1120,9 @@ const Parser = struct {
             const expr_node = try p.expectNode(parseExpr, .{
                 .ExpectedExpr = .{ .token = p.tok_i },
             });
-            const node = try p.arena.allocator.create(Node.PrefixOp);
+            const node = try p.arena.allocator.create(Node.Resume);
             node.* = .{
                 .op_token = token,
-                .op = .Resume,
                 .rhs = expr_node,
             };
             return &node.base;
@@ -2413,24 +2412,25 @@ const Parser = struct {
     ///      / KEYWORD_await
     fn parsePrefixOp(p: *Parser) !?*Node {
         const token = p.nextToken();
-        const op: Node.PrefixOp.Op = switch (p.token_ids[token]) {
-            .Bang => .BoolNot,
-            .Minus => .Negation,
-            .Tilde => .BitNot,
-            .MinusPercent => .NegationWrap,
-            .Ampersand => .AddressOf,
-            .Keyword_try => .Try,
-            .Keyword_await => .Await,
+        switch (p.token_ids[token]) {
+            .Bang => return p.allocSimplePrefixOp(.BoolNot, token),
+            .Minus => return p.allocSimplePrefixOp(.Negation, token),
+            .Tilde => return p.allocSimplePrefixOp(.BitNot, token),
+            .MinusPercent => return p.allocSimplePrefixOp(.NegationWrap, token),
+            .Ampersand => return p.allocSimplePrefixOp(.AddressOf, token),
+            .Keyword_try => return p.allocSimplePrefixOp(.Try, token),
+            .Keyword_await => return p.allocSimplePrefixOp(.Await, token),
             else => {
                 p.putBackToken(token);
                 return null;
             },
-        };
+        }
+    }
 
-        const node = try p.arena.allocator.create(Node.PrefixOp);
+    fn allocSimplePrefixOp(p: *Parser, comptime tag: Node.Id, token: TokenIndex) !?*Node {
+        const node = try p.arena.allocator.create(Node.SimplePrefixOp(tag));
         node.* = .{
             .op_token = token,
-            .op = op,
             .rhs = undefined, // set by caller
         };
         return &node.base;
@@ -2450,19 +2450,14 @@ const Parser = struct {
     ///      / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
     fn parsePrefixTypeOp(p: *Parser) !?*Node {
         if (p.eatToken(.QuestionMark)) |token| {
-            const node = try p.arena.allocator.create(Node.PrefixOp);
+            const node = try p.arena.allocator.create(Node.OptionalType);
             node.* = .{
                 .op_token = token,
-                .op = .OptionalType,
                 .rhs = undefined, // set by caller
             };
             return &node.base;
         }
 
-        // TODO: Returning a AnyFrameType instead of PrefixOp makes casting and setting .rhs or
-        //       .return_type more difficult for the caller (see parsePrefixOpExpr helper).
-        //       Consider making the AnyFrameType a member of PrefixOp and add a
-        //       PrefixOp.AnyFrameType variant?
         if (p.eatToken(.Keyword_anyframe)) |token| {
             const arrow = p.eatToken(.Arrow) orelse {
                 p.putBackToken(token);
@@ -2482,11 +2477,15 @@ const Parser = struct {
         if (try p.parsePtrTypeStart()) |node| {
             // If the token encountered was **, there will be two nodes instead of one.
             // The attributes should be applied to the rightmost operator.
-            const prefix_op = node.cast(Node.PrefixOp).?;
-            var ptr_info = if (p.token_ids[prefix_op.op_token] == .AsteriskAsterisk)
-                &prefix_op.rhs.cast(Node.PrefixOp).?.op.PtrType
+            var ptr_info = if (node.cast(Node.PtrType)) |ptr_type|
+                if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk)
+                    &ptr_type.rhs.cast(Node.PtrType).?.ptr_info
+                else
+                    &ptr_type.ptr_info
+            else if (node.cast(Node.SliceType)) |slice_type|
+                &slice_type.ptr_info
             else
-                &prefix_op.op.PtrType;
+                unreachable;
 
             while (true) {
                 if (p.eatToken(.Keyword_align)) |align_token| {
@@ -2505,7 +2504,7 @@ const Parser = struct {
                             .ExpectedIntegerLiteral = .{ .token = p.tok_i },
                         });
 
-                        break :bit_range_value Node.PrefixOp.PtrInfo.Align.BitRange{
+                        break :bit_range_value ast.PtrInfo.Align.BitRange{
                             .start = range_start,
                             .end = range_end,
                         };
@@ -2519,7 +2518,7 @@ const Parser = struct {
                         continue;
                     }
 
-                    ptr_info.align_info = Node.PrefixOp.PtrInfo.Align{
+                    ptr_info.align_info = ast.PtrInfo.Align{
                         .node = expr_node,
                         .bit_range = bit_range,
                     };
@@ -2563,58 +2562,54 @@ const Parser = struct {
         }
 
         if (try p.parseArrayTypeStart()) |node| {
-            switch (node.cast(Node.PrefixOp).?.op) {
-                .ArrayType => {},
-                .SliceType => |*slice_type| {
-                    // Collect pointer qualifiers in any order, but disallow duplicates
-                    while (true) {
-                        if (try p.parseByteAlign()) |align_expr| {
-                            if (slice_type.align_info != null) {
-                                try p.errors.append(p.gpa, .{
-                                    .ExtraAlignQualifier = .{ .token = p.tok_i - 1 },
-                                });
-                                continue;
-                            }
-                            slice_type.align_info = Node.PrefixOp.PtrInfo.Align{
-                                .node = align_expr,
-                                .bit_range = null,
-                            };
+            if (node.cast(Node.SliceType)) |slice_type| {
+                // Collect pointer qualifiers in any order, but disallow duplicates
+                while (true) {
+                    if (try p.parseByteAlign()) |align_expr| {
+                        if (slice_type.ptr_info.align_info != null) {
+                            try p.errors.append(p.gpa, .{
+                                .ExtraAlignQualifier = .{ .token = p.tok_i - 1 },
+                            });
                             continue;
                         }
-                        if (p.eatToken(.Keyword_const)) |const_token| {
-                            if (slice_type.const_token != null) {
-                                try p.errors.append(p.gpa, .{
-                                    .ExtraConstQualifier = .{ .token = p.tok_i - 1 },
-                                });
-                                continue;
-                            }
-                            slice_type.const_token = const_token;
+                        slice_type.ptr_info.align_info = ast.PtrInfo.Align{
+                            .node = align_expr,
+                            .bit_range = null,
+                        };
+                        continue;
+                    }
+                    if (p.eatToken(.Keyword_const)) |const_token| {
+                        if (slice_type.ptr_info.const_token != null) {
+                            try p.errors.append(p.gpa, .{
+                                .ExtraConstQualifier = .{ .token = p.tok_i - 1 },
+                            });
                             continue;
                         }
-                        if (p.eatToken(.Keyword_volatile)) |volatile_token| {
-                            if (slice_type.volatile_token != null) {
-                                try p.errors.append(p.gpa, .{
-                                    .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 },
-                                });
-                                continue;
-                            }
-                            slice_type.volatile_token = volatile_token;
+                        slice_type.ptr_info.const_token = const_token;
+                        continue;
+                    }
+                    if (p.eatToken(.Keyword_volatile)) |volatile_token| {
+                        if (slice_type.ptr_info.volatile_token != null) {
+                            try p.errors.append(p.gpa, .{
+                                .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 },
+                            });
                             continue;
                         }
-                        if (p.eatToken(.Keyword_allowzero)) |allowzero_token| {
-                            if (slice_type.allowzero_token != null) {
-                                try p.errors.append(p.gpa, .{
-                                    .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 },
-                                });
-                                continue;
-                            }
-                            slice_type.allowzero_token = allowzero_token;
+                        slice_type.ptr_info.volatile_token = volatile_token;
+                        continue;
+                    }
+                    if (p.eatToken(.Keyword_allowzero)) |allowzero_token| {
+                        if (slice_type.ptr_info.allowzero_token != null) {
+                            try p.errors.append(p.gpa, .{
+                                .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 },
+                            });
                             continue;
                         }
-                        break;
+                        slice_type.ptr_info.allowzero_token = allowzero_token;
+                        continue;
                     }
-                },
-                else => unreachable,
+                    break;
+                }
             }
             return node;
         }
@@ -2728,29 +2723,32 @@ const Parser = struct {
             null;
         const rbracket = try p.expectToken(.RBracket);
 
-        const op: Node.PrefixOp.Op = if (expr) |len_expr|
-            .{
-                .ArrayType = .{
+        if (expr) |len_expr| {
+            if (sentinel) |s| {
+                const node = try p.arena.allocator.create(Node.ArrayTypeSentinel);
+                node.* = .{
+                    .op_token = lbracket,
+                    .rhs = undefined, // set by caller
                     .len_expr = len_expr,
-                    .sentinel = sentinel,
-                },
+                    .sentinel = s,
+                };
+                return &node.base;
+            } else {
+                const node = try p.arena.allocator.create(Node.ArrayType);
+                node.* = .{
+                    .op_token = lbracket,
+                    .rhs = undefined, // set by caller
+                    .len_expr = len_expr,
+                };
+                return &node.base;
             }
-        else
-            .{
-                .SliceType = Node.PrefixOp.PtrInfo{
-                    .allowzero_token = null,
-                    .align_info = null,
-                    .const_token = null,
-                    .volatile_token = null,
-                    .sentinel = sentinel,
-                },
-            };
+        }
 
-        const node = try p.arena.allocator.create(Node.PrefixOp);
+        const node = try p.arena.allocator.create(Node.SliceType);
         node.* = .{
             .op_token = lbracket,
-            .op = op,
             .rhs = undefined, // set by caller
+            .ptr_info = .{ .sentinel = sentinel },
         };
         return &node.base;
     }
@@ -2768,28 +2766,26 @@ const Parser = struct {
                 })
             else
                 null;
-            const node = try p.arena.allocator.create(Node.PrefixOp);
+            const node = try p.arena.allocator.create(Node.PtrType);
             node.* = .{
                 .op_token = asterisk,
-                .op = .{ .PtrType = .{ .sentinel = sentinel } },
                 .rhs = undefined, // set by caller
+                .ptr_info = .{ .sentinel = sentinel },
             };
             return &node.base;
         }
 
         if (p.eatToken(.AsteriskAsterisk)) |double_asterisk| {
-            const node = try p.arena.allocator.create(Node.PrefixOp);
+            const node = try p.arena.allocator.create(Node.PtrType);
             node.* = .{
                 .op_token = double_asterisk,
-                .op = .{ .PtrType = .{} },
                 .rhs = undefined, // set by caller
             };
 
             // Special case for **, which is its own token
-            const child = try p.arena.allocator.create(Node.PrefixOp);
+            const child = try p.arena.allocator.create(Node.PtrType);
             child.* = .{
                 .op_token = double_asterisk,
-                .op = .{ .PtrType = .{} },
                 .rhs = undefined, // set by caller
             };
             node.rhs = &child.base;
@@ -2808,10 +2804,9 @@ const Parser = struct {
                     p.putBackToken(ident);
                 } else {
                     _ = try p.expectToken(.RBracket);
-                    const node = try p.arena.allocator.create(Node.PrefixOp);
+                    const node = try p.arena.allocator.create(Node.PtrType);
                     node.* = .{
                         .op_token = lbracket,
-                        .op = .{ .PtrType = .{} },
                         .rhs = undefined, // set by caller
                     };
                     return &node.base;
@@ -2824,11 +2819,11 @@ const Parser = struct {
             else
                 null;
             _ = try p.expectToken(.RBracket);
-            const node = try p.arena.allocator.create(Node.PrefixOp);
+            const node = try p.arena.allocator.create(Node.PtrType);
             node.* = .{
                 .op_token = lbracket,
-                .op = .{ .PtrType = .{ .sentinel = sentinel } },
                 .rhs = undefined, // set by caller
+                .ptr_info = .{ .sentinel = sentinel },
             };
             return &node.base;
         }
@@ -3146,10 +3141,9 @@ const Parser = struct {
 
     fn parseTry(p: *Parser) !?*Node {
         const token = p.eatToken(.Keyword_try) orelse return null;
-        const node = try p.arena.allocator.create(Node.PrefixOp);
+        const node = try p.arena.allocator.create(Node.Try);
         node.* = .{
             .op_token = token,
-            .op = .Try,
             .rhs = undefined, // set by caller
         };
         return &node.base;
@@ -3228,15 +3222,87 @@ const Parser = struct {
             var rightmost_op = first_op;
             while (true) {
                 switch (rightmost_op.id) {
-                    .PrefixOp => {
-                        var prefix_op = rightmost_op.cast(Node.PrefixOp).?;
+                    .AddressOf => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.AddressOf).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .Await => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.Await).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .BitNot => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.BitNot).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .BoolNot => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.BoolNot).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .OptionalType => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.OptionalType).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .Negation => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.Negation).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .NegationWrap => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.NegationWrap).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .Resume => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.Resume).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .Try => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.Try).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .ArrayType => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.ArrayType).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .ArrayTypeSentinel => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.ArrayTypeSentinel).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .SliceType => {
+                        if (try opParseFn(p)) |rhs| {
+                            rightmost_op.cast(Node.SliceType).?.rhs = rhs;
+                            rightmost_op = rhs;
+                        } else break;
+                    },
+                    .PtrType => {
+                        var ptr_type = rightmost_op.cast(Node.PtrType).?;
                         // If the token encountered was **, there will be two nodes
-                        if (p.token_ids[prefix_op.op_token] == .AsteriskAsterisk) {
-                            rightmost_op = prefix_op.rhs;
-                            prefix_op = rightmost_op.cast(Node.PrefixOp).?;
+                        if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk) {
+                            rightmost_op = ptr_type.rhs;
+                            ptr_type = rightmost_op.cast(Node.PtrType).?;
                         }
                         if (try opParseFn(p)) |rhs| {
-                            prefix_op.rhs = rhs;
+                            ptr_type.rhs = rhs;
                             rightmost_op = rhs;
                         } else break;
                     },
@@ -3253,8 +3319,80 @@ const Parser = struct {
 
             // If any prefix op existed, a child node on the RHS is required
             switch (rightmost_op.id) {
-                .PrefixOp => {
-                    const prefix_op = rightmost_op.cast(Node.PrefixOp).?;
+                .AddressOf => {
+                    const prefix_op = rightmost_op.cast(Node.AddressOf).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .Await => {
+                    const prefix_op = rightmost_op.cast(Node.Await).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .BitNot => {
+                    const prefix_op = rightmost_op.cast(Node.BitNot).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .BoolNot => {
+                    const prefix_op = rightmost_op.cast(Node.BoolNot).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .OptionalType => {
+                    const prefix_op = rightmost_op.cast(Node.OptionalType).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .Negation => {
+                    const prefix_op = rightmost_op.cast(Node.Negation).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .NegationWrap => {
+                    const prefix_op = rightmost_op.cast(Node.NegationWrap).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .Resume => {
+                    const prefix_op = rightmost_op.cast(Node.Resume).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .Try => {
+                    const prefix_op = rightmost_op.cast(Node.Try).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .ArrayType => {
+                    const prefix_op = rightmost_op.cast(Node.ArrayType).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .ArrayTypeSentinel => {
+                    const prefix_op = rightmost_op.cast(Node.ArrayTypeSentinel).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .PtrType => {
+                    const prefix_op = rightmost_op.cast(Node.PtrType).?;
+                    prefix_op.rhs = try p.expectNode(childParseFn, .{
+                        .InvalidToken = .{ .token = p.tok_i },
+                    });
+                },
+                .SliceType => {
+                    const prefix_op = rightmost_op.cast(Node.SliceType).?;
                     prefix_op.rhs = try p.expectNode(childParseFn, .{
                         .InvalidToken = .{ .token = p.tok_i },
                     });
lib/std/zig/render.zig
@@ -468,166 +468,192 @@ fn renderExpression(
             return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space);
         },
 
-        .PrefixOp => {
-            const prefix_op_node = @fieldParentPtr(ast.Node.PrefixOp, "base", base);
-
-            switch (prefix_op_node.op) {
-                .PtrType => |ptr_info| {
-                    const op_tok_id = tree.token_ids[prefix_op_node.op_token];
-                    switch (op_tok_id) {
-                        .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'),
-                        .LBracket => if (tree.token_ids[prefix_op_node.op_token + 2] == .Identifier)
-                            try stream.writeAll("[*c")
-                        else
-                            try stream.writeAll("[*"),
-                        else => unreachable,
-                    }
-                    if (ptr_info.sentinel) |sentinel| {
-                        const colon_token = tree.prevToken(sentinel.firstToken());
-                        try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
-                        const sentinel_space = switch (op_tok_id) {
-                            .LBracket => Space.None,
-                            else => Space.Space,
-                        };
-                        try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space);
-                    }
-                    switch (op_tok_id) {
-                        .Asterisk, .AsteriskAsterisk => {},
-                        .LBracket => try stream.writeByte(']'),
-                        else => unreachable,
-                    }
-                    if (ptr_info.allowzero_token) |allowzero_token| {
-                        try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
-                    }
-                    if (ptr_info.align_info) |align_info| {
-                        const lparen_token = tree.prevToken(align_info.node.firstToken());
-                        const align_token = tree.prevToken(lparen_token);
-
-                        try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
-                        try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
-
-                        try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
-
-                        if (align_info.bit_range) |bit_range| {
-                            const colon1 = tree.prevToken(bit_range.start.firstToken());
-                            const colon2 = tree.prevToken(bit_range.end.firstToken());
+        .BitNot => {
+            const bit_not = @fieldParentPtr(ast.Node.BitNot, "base", base);
+            try renderToken(tree, stream, bit_not.op_token, indent, start_col, Space.None);
+            return renderExpression(allocator, stream, tree, indent, start_col, bit_not.rhs, space);
+        },
+        .BoolNot => {
+            const bool_not = @fieldParentPtr(ast.Node.BoolNot, "base", base);
+            try renderToken(tree, stream, bool_not.op_token, indent, start_col, Space.None);
+            return renderExpression(allocator, stream, tree, indent, start_col, bool_not.rhs, space);
+        },
+        .Negation => {
+            const negation = @fieldParentPtr(ast.Node.Negation, "base", base);
+            try renderToken(tree, stream, negation.op_token, indent, start_col, Space.None);
+            return renderExpression(allocator, stream, tree, indent, start_col, negation.rhs, space);
+        },
+        .NegationWrap => {
+            const negation_wrap = @fieldParentPtr(ast.Node.NegationWrap, "base", base);
+            try renderToken(tree, stream, negation_wrap.op_token, indent, start_col, Space.None);
+            return renderExpression(allocator, stream, tree, indent, start_col, negation_wrap.rhs, space);
+        },
+        .OptionalType => {
+            const opt_type = @fieldParentPtr(ast.Node.OptionalType, "base", base);
+            try renderToken(tree, stream, opt_type.op_token, indent, start_col, Space.None);
+            return renderExpression(allocator, stream, tree, indent, start_col, opt_type.rhs, space);
+        },
+        .AddressOf => {
+            const addr_of = @fieldParentPtr(ast.Node.AddressOf, "base", base);
+            try renderToken(tree, stream, addr_of.op_token, indent, start_col, Space.None);
+            return renderExpression(allocator, stream, tree, indent, start_col, addr_of.rhs, space);
+        },
+        .Try => {
+            const try_node = @fieldParentPtr(ast.Node.Try, "base", base);
+            try renderToken(tree, stream, try_node.op_token, indent, start_col, Space.Space);
+            return renderExpression(allocator, stream, tree, indent, start_col, try_node.rhs, space);
+        },
+        .Resume => {
+            const resume_node = @fieldParentPtr(ast.Node.Resume, "base", base);
+            try renderToken(tree, stream, resume_node.op_token, indent, start_col, Space.Space);
+            return renderExpression(allocator, stream, tree, indent, start_col, resume_node.rhs, space);
+        },
+        .Await => {
+            const await_node = @fieldParentPtr(ast.Node.Await, "base", base);
+            try renderToken(tree, stream, await_node.op_token, indent, start_col, Space.Space);
+            return renderExpression(allocator, stream, tree, indent, start_col, await_node.rhs, space);
+        },
 
-                            try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
-                            try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
-                            try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
-                            try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
+        .ArrayType => {
+            const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base);
+            return renderArrayType(
+                allocator,
+                stream,
+                tree,
+                indent,
+                start_col,
+                array_type.op_token,
+                array_type.rhs,
+                array_type.len_expr,
+                null,
+                space,
+            );
+        },
+        .ArrayTypeSentinel => {
+            const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base);
+            return renderArrayType(
+                allocator,
+                stream,
+                tree,
+                indent,
+                start_col,
+                array_type.op_token,
+                array_type.rhs,
+                array_type.len_expr,
+                array_type.sentinel,
+                space,
+            );
+        },
 
-                            const rparen_token = tree.nextToken(bit_range.end.lastToken());
-                            try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
-                        } else {
-                            const rparen_token = tree.nextToken(align_info.node.lastToken());
-                            try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
-                        }
-                    }
-                    if (ptr_info.const_token) |const_token| {
-                        try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const
-                    }
-                    if (ptr_info.volatile_token) |volatile_token| {
-                        try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile
-                    }
-                },
+        .PtrType => {
+            const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base);
+            const op_tok_id = tree.token_ids[ptr_type.op_token];
+            switch (op_tok_id) {
+                .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'),
+                .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier)
+                    try stream.writeAll("[*c")
+                else
+                    try stream.writeAll("[*"),
+                else => unreachable,
+            }
+            if (ptr_type.ptr_info.sentinel) |sentinel| {
+                const colon_token = tree.prevToken(sentinel.firstToken());
+                try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
+                const sentinel_space = switch (op_tok_id) {
+                    .LBracket => Space.None,
+                    else => Space.Space,
+                };
+                try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space);
+            }
+            switch (op_tok_id) {
+                .Asterisk, .AsteriskAsterisk => {},
+                .LBracket => try stream.writeByte(']'),
+                else => unreachable,
+            }
+            if (ptr_type.ptr_info.allowzero_token) |allowzero_token| {
+                try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
+            }
+            if (ptr_type.ptr_info.align_info) |align_info| {
+                const lparen_token = tree.prevToken(align_info.node.firstToken());
+                const align_token = tree.prevToken(lparen_token);
 
-                .SliceType => |ptr_info| {
-                    try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None); // [
-                    if (ptr_info.sentinel) |sentinel| {
-                        const colon_token = tree.prevToken(sentinel.firstToken());
-                        try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
-                        try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
-                        try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ]
-                    } else {
-                        try renderToken(tree, stream, tree.nextToken(prefix_op_node.op_token), indent, start_col, Space.None); // ]
-                    }
+                try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
+                try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
 
-                    if (ptr_info.allowzero_token) |allowzero_token| {
-                        try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
-                    }
-                    if (ptr_info.align_info) |align_info| {
-                        const lparen_token = tree.prevToken(align_info.node.firstToken());
-                        const align_token = tree.prevToken(lparen_token);
+                try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
 
-                        try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
-                        try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
+                if (align_info.bit_range) |bit_range| {
+                    const colon1 = tree.prevToken(bit_range.start.firstToken());
+                    const colon2 = tree.prevToken(bit_range.end.firstToken());
 
-                        try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
+                    try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
+                    try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
+                    try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
+                    try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
 
-                        if (align_info.bit_range) |bit_range| {
-                            const colon1 = tree.prevToken(bit_range.start.firstToken());
-                            const colon2 = tree.prevToken(bit_range.end.firstToken());
+                    const rparen_token = tree.nextToken(bit_range.end.lastToken());
+                    try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
+                } else {
+                    const rparen_token = tree.nextToken(align_info.node.lastToken());
+                    try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
+                }
+            }
+            if (ptr_type.ptr_info.const_token) |const_token| {
+                try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const
+            }
+            if (ptr_type.ptr_info.volatile_token) |volatile_token| {
+                try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile
+            }
+            return renderExpression(allocator, stream, tree, indent, start_col, ptr_type.rhs, space);
+        },
 
-                            try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
-                            try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
-                            try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
-                            try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
+        .SliceType => {
+            const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base);
+            try renderToken(tree, stream, slice_type.op_token, indent, start_col, Space.None); // [
+            if (slice_type.ptr_info.sentinel) |sentinel| {
+                const colon_token = tree.prevToken(sentinel.firstToken());
+                try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
+                try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
+                try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ]
+            } else {
+                try renderToken(tree, stream, tree.nextToken(slice_type.op_token), indent, start_col, Space.None); // ]
+            }
 
-                            const rparen_token = tree.nextToken(bit_range.end.lastToken());
-                            try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
-                        } else {
-                            const rparen_token = tree.nextToken(align_info.node.lastToken());
-                            try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
-                        }
-                    }
-                    if (ptr_info.const_token) |const_token| {
-                        try renderToken(tree, stream, const_token, indent, start_col, Space.Space);
-                    }
-                    if (ptr_info.volatile_token) |volatile_token| {
-                        try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space);
-                    }
-                },
+            if (slice_type.ptr_info.allowzero_token) |allowzero_token| {
+                try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero
+            }
+            if (slice_type.ptr_info.align_info) |align_info| {
+                const lparen_token = tree.prevToken(align_info.node.firstToken());
+                const align_token = tree.prevToken(lparen_token);
 
-                .ArrayType => |array_info| {
-                    const lbracket = prefix_op_node.op_token;
-                    const rbracket = tree.nextToken(if (array_info.sentinel) |sentinel|
-                        sentinel.lastToken()
-                    else
-                        array_info.len_expr.lastToken());
+                try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align
+                try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
 
-                    try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
+                try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None);
 
-                    const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
-                    const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
-                    const new_indent = if (ends_with_comment) indent + indent_delta else indent;
-                    const new_space = if (ends_with_comment) Space.Newline else Space.None;
-                    try renderExpression(allocator, stream, tree, new_indent, start_col, array_info.len_expr, new_space);
-                    if (starts_with_comment) {
-                        try stream.writeByte('\n');
-                    }
-                    if (ends_with_comment or starts_with_comment) {
-                        try stream.writeByteNTimes(' ', indent);
-                    }
-                    if (array_info.sentinel) |sentinel| {
-                        const colon_token = tree.prevToken(sentinel.firstToken());
-                        try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
-                        try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
-                    }
-                    try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ]
-                },
-                .BitNot,
-                .BoolNot,
-                .Negation,
-                .NegationWrap,
-                .OptionalType,
-                .AddressOf,
-                => {
-                    try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None);
-                },
+                if (align_info.bit_range) |bit_range| {
+                    const colon1 = tree.prevToken(bit_range.start.firstToken());
+                    const colon2 = tree.prevToken(bit_range.end.firstToken());
 
-                .Try,
-                .Resume,
-                => {
-                    try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.Space);
-                },
+                    try renderToken(tree, stream, colon1, indent, start_col, Space.None); // :
+                    try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None);
+                    try renderToken(tree, stream, colon2, indent, start_col, Space.None); // :
+                    try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None);
 
-                .Await => |await_info| {
-                    try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.Space);
-                },
+                    const rparen_token = tree.nextToken(bit_range.end.lastToken());
+                    try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
+                } else {
+                    const rparen_token = tree.nextToken(align_info.node.lastToken());
+                    try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
+                }
             }
-
-            return renderExpression(allocator, stream, tree, indent, start_col, prefix_op_node.rhs, space);
+            if (slice_type.ptr_info.const_token) |const_token| {
+                try renderToken(tree, stream, const_token, indent, start_col, Space.Space);
+            }
+            if (slice_type.ptr_info.volatile_token) |volatile_token| {
+                try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space);
+            }
+            return renderExpression(allocator, stream, tree, indent, start_col, slice_type.rhs, space);
         },
 
         .ArrayInitializer, .ArrayInitializerDot => {
@@ -2057,6 +2083,46 @@ fn renderExpression(
     }
 }
 
+fn renderArrayType(
+    allocator: *mem.Allocator,
+    stream: anytype,
+    tree: *ast.Tree,
+    indent: usize,
+    start_col: *usize,
+    lbracket: ast.TokenIndex,
+    rhs: *ast.Node,
+    len_expr: *ast.Node,
+    opt_sentinel: ?*ast.Node,
+    space: Space,
+) (@TypeOf(stream).Error || Error)!void {
+    const rbracket = tree.nextToken(if (opt_sentinel) |sentinel|
+        sentinel.lastToken()
+    else
+        len_expr.lastToken());
+
+    try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [
+
+    const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment;
+    const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment;
+    const new_indent = if (ends_with_comment) indent + indent_delta else indent;
+    const new_space = if (ends_with_comment) Space.Newline else Space.None;
+    try renderExpression(allocator, stream, tree, new_indent, start_col, len_expr, new_space);
+    if (starts_with_comment) {
+        try stream.writeByte('\n');
+    }
+    if (ends_with_comment or starts_with_comment) {
+        try stream.writeByteNTimes(' ', indent);
+    }
+    if (opt_sentinel) |sentinel| {
+        const colon_token = tree.prevToken(sentinel.firstToken());
+        try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // :
+        try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None);
+    }
+    try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ]
+
+    return renderExpression(allocator, stream, tree, indent, start_col, rhs, space);
+}
+
 fn renderAsmOutput(
     allocator: *mem.Allocator,
     stream: anytype,
src-self-hosted/Module.zig
@@ -1308,10 +1308,18 @@ fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir
         .ControlFlowExpression => return self.astGenControlFlowExpression(scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)),
         .If => return self.astGenIf(scope, @fieldParentPtr(ast.Node.If, "base", ast_node)),
         .InfixOp => return self.astGenInfixOp(scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)),
+        .BoolNot => return self.astGenBoolNot(scope, @fieldParentPtr(ast.Node.BoolNot, "base", ast_node)),
         else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}),
     }
 }
 
+fn astGenBoolNot(self: *Module, scope: *Scope, node: *ast.Node.BoolNot) InnerError!*zir.Inst {
+    const operand = try self.astGenExpr(scope, node.rhs);
+    const tree = scope.tree();
+    const src = tree.token_locs[node.op_token].start;
+    return self.addZIRInst(scope, src, zir.Inst.BoolNot, .{ .operand = operand }, .{});
+}
+
 fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst {
     switch (infix_node.op) {
         .Assign => {
src-self-hosted/translate_c.zig
@@ -1561,7 +1561,7 @@ fn transImplicitCastExpr(
                 return maybeSuppressResult(rp, scope, result_used, sub_expr_node);
             }
 
-            const prefix_op = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
+            const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
             prefix_op.rhs = try transExpr(rp, scope, sub_expr, .used, .r_value);
 
             return maybeSuppressResult(rp, scope, result_used, &prefix_op.base);
@@ -1673,11 +1673,7 @@ fn isBoolRes(res: *ast.Node) bool {
 
             else => {},
         },
-        .PrefixOp => switch (@fieldParentPtr(ast.Node.PrefixOp, "base", res).op) {
-            .BoolNot => return true,
-
-            else => {},
-        },
+        .BoolNot => return true,
         .BoolLiteral => return true,
         .GroupedExpression => return isBoolRes(@fieldParentPtr(ast.Node.GroupedExpression, "base", res).expr),
         else => {},
@@ -2162,21 +2158,16 @@ fn transCreateNodeArrayType(
     source_loc: ZigClangSourceLocation,
     ty: *const ZigClangType,
     len: anytype,
-) TransError!*ast.Node {
-    var node = try transCreateNodePrefixOp(
-        rp.c,
-        .{
-            .ArrayType = .{
-                .len_expr = undefined,
-                .sentinel = null,
-            },
-        },
-        .LBracket,
-        "[",
-    );
-    node.op.ArrayType.len_expr = try transCreateNodeInt(rp.c, len);
+) !*ast.Node {
+    const node = try rp.c.arena.create(ast.Node.ArrayType);
+    const op_token = try appendToken(rp.c, .LBracket, "[");
+    const len_expr = try transCreateNodeInt(rp.c, len);
     _ = try appendToken(rp.c, .RBracket, "]");
-    node.rhs = try transType(rp, ty, source_loc);
+    node.* = .{
+        .op_token = op_token,
+        .rhs = try transType(rp, ty, source_loc),
+        .len_expr = len_expr,
+    };
     return &node.base;
 }
 
@@ -2449,7 +2440,7 @@ fn transDoWhileLoop(
         },
     };
     defer cond_scope.deinit();
-    const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!");
+    const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .BoolNot, .Bang, "!");
     prefix_op.rhs = try transBoolExpr(rp, &cond_scope.base, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true);
     _ = try appendToken(rp.c, .RParen, ")");
     if_node.condition = &prefix_op.base;
@@ -3036,7 +3027,7 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar
         else
             return transCreatePreCrement(rp, scope, stmt, .AssignSub, .MinusEqual, "-=", used),
         .AddrOf => {
-            const op_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
+            const op_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
             op_node.rhs = try transExpr(rp, scope, op_expr, used, .r_value);
             return &op_node.base;
         },
@@ -3052,7 +3043,7 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar
         .Plus => return transExpr(rp, scope, op_expr, used, .r_value),
         .Minus => {
             if (!qualTypeHasWrappingOverflow(ZigClangExpr_getType(op_expr))) {
-                const op_node = try transCreateNodePrefixOp(rp.c, .Negation, .Minus, "-");
+                const op_node = try transCreateNodeSimplePrefixOp(rp.c, .Negation, .Minus, "-");
                 op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
                 return &op_node.base;
             } else if (cIsUnsignedInteger(ZigClangExpr_getType(op_expr))) {
@@ -3065,12 +3056,12 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar
                 return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangUnaryOperator_getBeginLoc(stmt), "C negation with non float non integer", .{});
         },
         .Not => {
-            const op_node = try transCreateNodePrefixOp(rp.c, .BitNot, .Tilde, "~");
+            const op_node = try transCreateNodeSimplePrefixOp(rp.c, .BitNot, .Tilde, "~");
             op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
             return &op_node.base;
         },
         .LNot => {
-            const op_node = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!");
+            const op_node = try transCreateNodeSimplePrefixOp(rp.c, .BoolNot, .Bang, "!");
             op_node.rhs = try transBoolExpr(rp, scope, op_expr, .used, .r_value, true);
             return &op_node.base;
         },
@@ -3116,7 +3107,7 @@ fn transCreatePreCrement(
 
     const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
     node.eq_token = try appendToken(rp.c, .Equal, "=");
-    const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
+    const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
     rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
     node.init_node = &rhs_node.base;
     node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
@@ -3182,7 +3173,7 @@ fn transCreatePostCrement(
 
     const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
     node.eq_token = try appendToken(rp.c, .Equal, "=");
-    const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
+    const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
     rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value);
     node.init_node = &rhs_node.base;
     node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
@@ -3336,7 +3327,7 @@ fn transCreateCompoundAssign(
 
     const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
     node.eq_token = try appendToken(rp.c, .Equal, "=");
-    const addr_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
+    const addr_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&");
     addr_node.rhs = try transExpr(rp, scope, lhs, .used, .l_value);
     node.init_node = &addr_node.base;
     node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
@@ -3984,16 +3975,15 @@ fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []c
     return &field_access_node.base;
 }
 
-fn transCreateNodePrefixOp(
+fn transCreateNodeSimplePrefixOp(
     c: *Context,
-    op: ast.Node.PrefixOp.Op,
+    comptime tag: ast.Node.Id,
     op_tok_id: std.zig.Token.Id,
     bytes: []const u8,
-) !*ast.Node.PrefixOp {
-    const node = try c.arena.create(ast.Node.PrefixOp);
+) !*ast.Node.SimplePrefixOp(tag) {
+    const node = try c.arena.create(ast.Node.SimplePrefixOp(tag));
     node.* = .{
         .op_token = try appendToken(c, op_tok_id, bytes),
-        .op = op,
         .rhs = undefined, // translate and set afterward
     };
     return node;
@@ -4065,8 +4055,8 @@ fn transCreateNodePtrType(
     is_const: bool,
     is_volatile: bool,
     op_tok_id: std.zig.Token.Id,
-) !*ast.Node.PrefixOp {
-    const node = try c.arena.create(ast.Node.PrefixOp);
+) !*ast.Node.PtrType {
+    const node = try c.arena.create(ast.Node.PtrType);
     const op_token = switch (op_tok_id) {
         .LBracket => blk: {
             const lbracket = try appendToken(c, .LBracket, "[");
@@ -4086,11 +4076,9 @@ fn transCreateNodePtrType(
     };
     node.* = .{
         .op_token = op_token,
-        .op = .{
-            .PtrType = .{
-                .const_token = if (is_const) try appendToken(c, .Keyword_const, "const") else null,
-                .volatile_token = if (is_volatile) try appendToken(c, .Keyword_volatile, "volatile") else null,
-            },
+        .ptr_info = .{
+            .const_token = if (is_const) try appendToken(c, .Keyword_const, "const") else null,
+            .volatile_token = if (is_volatile) try appendToken(c, .Keyword_volatile, "volatile") else null,
         },
         .rhs = undefined, // translate and set afterward
     };
@@ -4569,12 +4557,12 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour
         .Pointer => {
             const child_qt = ZigClangType_getPointeeType(ty);
             if (qualTypeChildIsFnProto(child_qt)) {
-                const optional_node = try transCreateNodePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
+                const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
                 optional_node.rhs = try transQualType(rp, child_qt, source_loc);
                 return &optional_node.base;
             }
             if (typeIsOpaque(rp.c, ZigClangQualType_getTypePtr(child_qt), source_loc)) {
-                const optional_node = try transCreateNodePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
+                const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?");
                 const pointer_node = try transCreateNodePtrType(
                     rp.c,
                     ZigClangQualType_isConstQualified(child_qt),
@@ -4599,21 +4587,8 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour
 
             const size_ap_int = ZigClangConstantArrayType_getSize(const_arr_ty);
             const size = ZigClangAPInt_getLimitedValue(size_ap_int, math.maxInt(usize));
-            var node = try transCreateNodePrefixOp(
-                rp.c,
-                .{
-                    .ArrayType = .{
-                        .len_expr = undefined,
-                        .sentinel = null,
-                    },
-                },
-                .LBracket,
-                "[",
-            );
-            node.op.ArrayType.len_expr = try transCreateNodeInt(rp.c, size);
-            _ = try appendToken(rp.c, .RBracket, "]");
-            node.rhs = try transQualType(rp, ZigClangConstantArrayType_getElementType(const_arr_ty), source_loc);
-            return &node.base;
+            const elem_ty = ZigClangQualType_getTypePtr(ZigClangConstantArrayType_getElementType(const_arr_ty));
+            return try transCreateNodeArrayType(rp, source_loc, elem_ty, size);
         },
         .IncompleteArray => {
             const incomplete_array_ty = @ptrCast(*const ZigClangIncompleteArrayType, ty);
@@ -5824,7 +5799,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
                     if (prev_id == .Keyword_void) {
                         const ptr = try transCreateNodePtrType(c, false, false, .Asterisk);
                         ptr.rhs = node;
-                        const optional_node = try transCreateNodePrefixOp(c, .OptionalType, .QuestionMark, "?");
+                        const optional_node = try transCreateNodeSimplePrefixOp(c, .OptionalType, .QuestionMark, "?");
                         optional_node.rhs = &ptr.base;
                         return &optional_node.base;
                     } else {
@@ -5993,18 +5968,18 @@ fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
 
     switch (op_tok.id) {
         .Bang => {
-            const node = try transCreateNodePrefixOp(c, .BoolNot, .Bang, "!");
+            const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!");
             node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
             return &node.base;
         },
         .Minus => {
-            const node = try transCreateNodePrefixOp(c, .Negation, .Minus, "-");
+            const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-");
             node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
             return &node.base;
         },
         .Plus => return try parseCPrefixOpExpr(c, it, source, source_loc, scope),
         .Tilde => {
-            const node = try transCreateNodePrefixOp(c, .BitNot, .Tilde, "~");
+            const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~");
             node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
             return &node.base;
         },
@@ -6013,7 +5988,7 @@ fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8,
             return try transCreateNodePtrDeref(c, node);
         },
         .Ampersand => {
-            const node = try transCreateNodePrefixOp(c, .AddressOf, .Ampersand, "&");
+            const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&");
             node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
             return &node.base;
         },
@@ -6034,29 +6009,49 @@ fn tokenSlice(c: *Context, token: ast.TokenIndex) []u8 {
 }
 
 fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node {
-    if (node.id == .ContainerDecl) {
-        return node;
-    } else if (node.id == .PrefixOp) {
-        return node;
-    } else if (node.cast(ast.Node.Identifier)) |ident| {
-        if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| {
-            if (value.cast(ast.Node.VarDecl)) |var_decl|
-                return getContainer(c, var_decl.init_node.?);
-        }
-    } else if (node.cast(ast.Node.InfixOp)) |infix| {
-        if (infix.op != .Period)
-            return null;
-        if (getContainerTypeOf(c, infix.lhs)) |ty_node| {
-            if (ty_node.cast(ast.Node.ContainerDecl)) |container| {
-                for (container.fieldsAndDecls()) |field_ref| {
-                    const field = field_ref.cast(ast.Node.ContainerField).?;
-                    const ident = infix.rhs.cast(ast.Node.Identifier).?;
-                    if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) {
-                        return getContainer(c, field.type_expr.?);
+    switch (node.id) {
+        .ContainerDecl,
+        .AddressOf,
+        .Await,
+        .BitNot,
+        .BoolNot,
+        .OptionalType,
+        .Negation,
+        .NegationWrap,
+        .Resume,
+        .Try,
+        .ArrayType,
+        .ArrayTypeSentinel,
+        .PtrType,
+        .SliceType,
+        => return node,
+
+        .Identifier => {
+            const ident = node.cast(ast.Node.Identifier).?;
+            if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| {
+                if (value.cast(ast.Node.VarDecl)) |var_decl|
+                    return getContainer(c, var_decl.init_node.?);
+            }
+        },
+
+        .InfixOp => {
+            const infix = node.cast(ast.Node.InfixOp).?;
+            if (infix.op != .Period)
+                return null;
+            if (getContainerTypeOf(c, infix.lhs)) |ty_node| {
+                if (ty_node.cast(ast.Node.ContainerDecl)) |container| {
+                    for (container.fieldsAndDecls()) |field_ref| {
+                        const field = field_ref.cast(ast.Node.ContainerField).?;
+                        const ident = infix.rhs.cast(ast.Node.Identifier).?;
+                        if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) {
+                            return getContainer(c, field.type_expr.?);
+                        }
                     }
                 }
             }
-        }
+        },
+
+        else => {},
     }
     return null;
 }
@@ -6091,11 +6086,9 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node {
 fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto {
     const init = if (ref.cast(ast.Node.VarDecl)) |v| v.init_node.? else return null;
     if (getContainerTypeOf(c, init)) |ty_node| {
-        if (ty_node.cast(ast.Node.PrefixOp)) |prefix| {
-            if (prefix.op == .OptionalType) {
-                if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| {
-                    return fn_proto;
-                }
+        if (ty_node.cast(ast.Node.OptionalType)) |prefix| {
+            if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| {
+                return fn_proto;
             }
         }
     }