Commit 804b51b179

Andrew Kelley <andrew@ziglang.org>
2020-07-15 10:38:31
stage2: VarDecl and FnProto take advantage of TrailerFlags API
These AST nodes now have a flags field and then a bunch of optional trailing objects. The end result is lower memory usage and consequently better performance. This is part of an ongoing effort to reduce the amount of memory parsed ASTs take up. Running `zig fmt` on the std lib: * cache-misses: 2,554,321 => 2,534,745 * instructions: 3,293,220,119 => 3,302,479,874 * peak memory: 74.0 MiB => 73.0 MiB Holding the entire std lib AST in memory at the same time: 93.9 MiB => 88.5 MiB
1 parent eac6280
lib/std/meta/trailer_flags.zig
@@ -33,17 +33,30 @@ pub fn TrailerFlags(comptime Fields: type) type {
             self.bits |= 1 << field_index;
         }
 
-        pub fn init(comptime names: anytype) Self {
+        /// `fields` is a struct with each field set to an optional value.
+        /// Missing fields are assumed to be `null`.
+        /// Only the non-null bits are observed and are used to set the flag bits.
+        pub fn init(fields: anytype) Self {
             var self: Self = .{ .bits = 0 };
-            inline for (@typeInfo(@TypeOf(names)).Struct.fields) |field| {
-                if (@field(names, field.name)) {
-                    const field_index = meta.fieldIndex(Fields, field.name).?;
-                    self.bits |= 1 << field_index;
-                }
+            inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| {
+                const opt: ?Field(field.name) = @field(fields, field.name);
+                const field_index = meta.fieldIndex(Fields, field.name).?;
+                self.bits |= @as(Int, @boolToInt(opt != null)) << field_index;
             }
             return self;
         }
 
+        /// `fields` is a struct with each field set to an optional value (same as `init`).
+        /// Missing fields are assumed to be `null`.
+        pub fn setMany(self: Self, p: [*]align(@alignOf(Fields)) u8, fields: anytype) void {
+            inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| {
+                const opt: ?Field(field.name) = @field(fields, field.name);
+                if (opt) |value| {
+                    self.set(p, field.name, value);
+                }
+            }
+        }
+
         pub fn set(
             self: Self,
             p: [*]align(@alignOf(Fields)) u8,
@@ -54,11 +67,15 @@ pub fn TrailerFlags(comptime Fields: type) type {
         }
 
         pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime name: []const u8) *Field(name) {
+            if (@sizeOf(Field(name)) == 0)
+                return undefined;
             const off = self.offset(p, name);
             return @ptrCast(*Field(name), @alignCast(@alignOf(Field(name)), p + off));
         }
 
         pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) *const Field(name) {
+            if (@sizeOf(Field(name)) == 0)
+                return undefined;
             const off = self.offset(p, name);
             return @ptrCast(*const Field(name), @alignCast(@alignOf(Field(name)), p + off));
         }
@@ -85,6 +102,8 @@ pub fn TrailerFlags(comptime Fields: type) type {
         pub fn sizeInBytes(self: Self) usize {
             var off: usize = 0;
             inline for (@typeInfo(Fields).Struct.fields) |field, i| {
+                if (@sizeOf(field.field_type) == 0)
+                    continue;
                 if ((self.bits & (1 << i)) != 0) {
                     off = mem.alignForwardGeneric(usize, off, @alignOf(field.field_type));
                     off += @sizeOf(field.field_type);
@@ -103,7 +122,7 @@ test "TrailerFlags" {
     });
     var flags = Flags.init(.{
         .b = true,
-        .c = true,
+        .c = 1234,
     });
     testing.expect(flags.sizeInBytes() == 16);
     const slice = try testing.allocator.allocAdvanced(u8, 8, flags.sizeInBytes(), .exact);
@@ -115,4 +134,13 @@ test "TrailerFlags" {
     testing.expect(flags.get(slice.ptr, "a") == null);
     testing.expect(!flags.get(slice.ptr, "b").?);
     testing.expect(flags.get(slice.ptr, "c").? == 12345678);
+
+    flags.setMany(slice.ptr, .{
+        .b = true,
+        .c = 5678,
+    });
+
+    testing.expect(flags.get(slice.ptr, "a") == null);
+    testing.expect(flags.get(slice.ptr, "b").?);
+    testing.expect(flags.get(slice.ptr, "c").? == 5678);
 }
lib/std/zig/ast.zig
@@ -675,42 +675,84 @@ pub const Node = struct {
         }
     };
 
+    /// Trailed in memory by possibly many things, with each optional thing
+    /// determined by a bit in `trailer_flags`.
     pub const VarDecl = struct {
         base: Node = Node{ .id = .VarDecl },
-        doc_comments: ?*DocComment,
-        visib_token: ?TokenIndex,
-        thread_local_token: ?TokenIndex,
-        name_token: TokenIndex,
-        eq_token: ?TokenIndex,
+        trailer_flags: TrailerFlags,
         mut_token: TokenIndex,
-        comptime_token: ?TokenIndex,
-        extern_export_token: ?TokenIndex,
-        lib_name: ?*Node,
-        type_node: ?*Node,
-        align_node: ?*Node,
-        section_node: ?*Node,
-        init_node: ?*Node,
+        name_token: TokenIndex,
         semicolon_token: TokenIndex,
 
+        pub const TrailerFlags = std.meta.TrailerFlags(struct {
+            doc_comments: *DocComment,
+            visib_token: TokenIndex,
+            thread_local_token: TokenIndex,
+            eq_token: TokenIndex,
+            comptime_token: TokenIndex,
+            extern_export_token: TokenIndex,
+            lib_name: *Node,
+            type_node: *Node,
+            align_node: *Node,
+            section_node: *Node,
+            init_node: *Node,
+        });
+
+        pub const RequiredFields = struct {
+            mut_token: TokenIndex,
+            name_token: TokenIndex,
+            semicolon_token: TokenIndex,
+        };
+
+        pub fn getTrailer(self: *const VarDecl, comptime name: []const u8) ?TrailerFlags.Field(name) {
+            const trailers_start = @ptrCast([*]const u8, self) + @sizeOf(VarDecl);
+            return self.trailer_flags.get(trailers_start, name);
+        }
+
+        pub fn setTrailer(self: *VarDecl, comptime name: []const u8, value: TrailerFlags.Field(name)) void {
+            const trailers_start = @ptrCast([*]u8, self) + @sizeOf(VarDecl);
+            self.trailer_flags.set(trailers_start, name, value);
+        }
+
+        pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: anytype) !*VarDecl {
+            const trailer_flags = TrailerFlags.init(trailers);
+            const bytes = try allocator.alignedAlloc(u8, @alignOf(VarDecl), sizeInBytes(trailer_flags));
+            const var_decl = @ptrCast(*VarDecl, bytes.ptr);
+            var_decl.* = .{
+                .trailer_flags = trailer_flags,
+                .mut_token = required.mut_token,
+                .name_token = required.name_token,
+                .semicolon_token = required.semicolon_token,
+            };
+            const trailers_start = bytes.ptr + @sizeOf(VarDecl);
+            trailer_flags.setMany(trailers_start, trailers);
+            return var_decl;
+        }
+
+        pub fn destroy(self: *VarDecl, allocator: *mem.Allocator) void {
+            const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.trailer_flags)];
+            allocator.free(bytes);
+        }
+
         pub fn iterate(self: *const VarDecl, index: usize) ?*Node {
             var i = index;
 
-            if (self.type_node) |type_node| {
+            if (self.getTrailer("type_node")) |type_node| {
                 if (i < 1) return type_node;
                 i -= 1;
             }
 
-            if (self.align_node) |align_node| {
+            if (self.getTrailer("align_node")) |align_node| {
                 if (i < 1) return align_node;
                 i -= 1;
             }
 
-            if (self.section_node) |section_node| {
+            if (self.getTrailer("section_node")) |section_node| {
                 if (i < 1) return section_node;
                 i -= 1;
             }
 
-            if (self.init_node) |init_node| {
+            if (self.getTrailer("init_node")) |init_node| {
                 if (i < 1) return init_node;
                 i -= 1;
             }
@@ -719,17 +761,21 @@ pub const Node = struct {
         }
 
         pub fn firstToken(self: *const VarDecl) TokenIndex {
-            if (self.visib_token) |visib_token| return visib_token;
-            if (self.thread_local_token) |thread_local_token| return thread_local_token;
-            if (self.comptime_token) |comptime_token| return comptime_token;
-            if (self.extern_export_token) |extern_export_token| return extern_export_token;
-            assert(self.lib_name == null);
+            if (self.getTrailer("visib_token")) |visib_token| return visib_token;
+            if (self.getTrailer("thread_local_token")) |thread_local_token| return thread_local_token;
+            if (self.getTrailer("comptime_token")) |comptime_token| return comptime_token;
+            if (self.getTrailer("extern_export_token")) |extern_export_token| return extern_export_token;
+            assert(self.getTrailer("lib_name") == null);
             return self.mut_token;
         }
 
         pub fn lastToken(self: *const VarDecl) TokenIndex {
             return self.semicolon_token;
         }
+
+        fn sizeInBytes(trailer_flags: TrailerFlags) usize {
+            return @sizeOf(VarDecl) + trailer_flags.sizeInBytes();
+        }
     };
 
     pub const Use = struct {
@@ -972,25 +1018,34 @@ pub const Node = struct {
     };
 
     /// The params are directly after the FnProto in memory.
-    /// TODO have a flags field for the optional nodes, and have them appended
-    /// before or after the parameters in memory.
+    /// Next, each optional thing determined by a bit in `trailer_flags`.
     pub const FnProto = struct {
         base: Node = Node{ .id = .FnProto },
-        doc_comments: ?*DocComment,
-        visib_token: ?TokenIndex,
+        trailer_flags: TrailerFlags,
         fn_token: TokenIndex,
-        name_token: ?TokenIndex,
         params_len: NodeIndex,
         return_type: ReturnType,
-        var_args_token: ?TokenIndex,
-        extern_export_inline_token: ?TokenIndex,
-        body_node: ?*Node,
-        lib_name: ?*Node, // populated if this is an extern declaration
-        align_expr: ?*Node, // populated if align(A) is present
-        section_expr: ?*Node, // populated if linksection(A) is present
-        callconv_expr: ?*Node, // populated if callconv(A) is present
-        is_extern_prototype: bool = false, // TODO: Remove once extern fn rewriting is
-        is_async: bool = false, // TODO: remove once async fn rewriting is
+
+        pub const TrailerFlags = std.meta.TrailerFlags(struct {
+            doc_comments: *DocComment,
+            body_node: *Node,
+            lib_name: *Node, // populated if this is an extern declaration
+            align_expr: *Node, // populated if align(A) is present
+            section_expr: *Node, // populated if linksection(A) is present
+            callconv_expr: *Node, // populated if callconv(A) is present
+            visib_token: TokenIndex,
+            name_token: TokenIndex,
+            var_args_token: TokenIndex,
+            extern_export_inline_token: TokenIndex,
+            is_extern_prototype: void, // TODO: Remove once extern fn rewriting is
+            is_async: void, // TODO: remove once async fn rewriting is
+        });
+
+        pub const RequiredFields = struct {
+            fn_token: TokenIndex,
+            params_len: NodeIndex,
+            return_type: ReturnType,
+        };
 
         pub const ReturnType = union(enum) {
             Explicit: *Node,
@@ -1007,7 +1062,6 @@ pub const Node = struct {
 
             pub const ParamType = union(enum) {
                 any_type: *Node,
-                var_args: TokenIndex,
                 type_expr: *Node,
             };
 
@@ -1016,7 +1070,6 @@ pub const Node = struct {
 
                 if (i < 1) {
                     switch (self.param_type) {
-                        .var_args => return null,
                         .any_type, .type_expr => |node| return node,
                     }
                 }
@@ -1030,34 +1083,79 @@ pub const Node = struct {
                 if (self.noalias_token) |noalias_token| return noalias_token;
                 if (self.name_token) |name_token| return name_token;
                 switch (self.param_type) {
-                    .var_args => |tok| return tok,
                     .any_type, .type_expr => |node| return node.firstToken(),
                 }
             }
 
             pub fn lastToken(self: *const ParamDecl) TokenIndex {
                 switch (self.param_type) {
-                    .var_args => |tok| return tok,
                     .any_type, .type_expr => |node| return node.lastToken(),
                 }
             }
         };
 
+        /// For debugging purposes.
+        pub fn dump(self: *const FnProto) void {
+            const trailers_start = @alignCast(
+                @alignOf(ParamDecl),
+                @ptrCast([*]const u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len,
+            );
+            std.debug.print("{*} flags: {b} name_token: {} {*} params_len: {}\n", .{
+                self,
+                self.trailer_flags.bits,
+                self.getTrailer("name_token"),
+                self.trailer_flags.ptrConst(trailers_start, "name_token"),
+                self.params_len,
+            });
+        }
+
+        pub fn getTrailer(self: *const FnProto, comptime name: []const u8) ?TrailerFlags.Field(name) {
+            const trailers_start = @alignCast(
+                @alignOf(ParamDecl),
+                @ptrCast([*]const u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len,
+            );
+            return self.trailer_flags.get(trailers_start, name);
+        }
+
+        pub fn setTrailer(self: *FnProto, comptime name: []const u8, value: TrailerFlags.Field(name)) void {
+            const trailers_start = @alignCast(
+                @alignOf(ParamDecl),
+                @ptrCast([*]u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len,
+            );
+            self.trailer_flags.set(trailers_start, name, value);
+        }
+
         /// After this the caller must initialize the params list.
-        pub fn alloc(allocator: *mem.Allocator, params_len: NodeIndex) !*FnProto {
-            const bytes = try allocator.alignedAlloc(u8, @alignOf(FnProto), sizeInBytes(params_len));
-            return @ptrCast(*FnProto, bytes.ptr);
+        pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: anytype) !*FnProto {
+            const trailer_flags = TrailerFlags.init(trailers);
+            const bytes = try allocator.alignedAlloc(u8, @alignOf(FnProto), sizeInBytes(
+                required.params_len,
+                trailer_flags,
+            ));
+            const fn_proto = @ptrCast(*FnProto, bytes.ptr);
+            fn_proto.* = .{
+                .trailer_flags = trailer_flags,
+                .fn_token = required.fn_token,
+                .params_len = required.params_len,
+                .return_type = required.return_type,
+            };
+            const trailers_start = @alignCast(
+                @alignOf(ParamDecl),
+                bytes.ptr + @sizeOf(FnProto) + @sizeOf(ParamDecl) * required.params_len,
+            );
+            trailer_flags.setMany(trailers_start, trailers);
+            return fn_proto;
         }
 
-        pub fn free(self: *FnProto, allocator: *mem.Allocator) void {
-            const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.params_len)];
+        pub fn destroy(self: *FnProto, allocator: *mem.Allocator) void {
+            const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.params_len, self.trailer_flags)];
             allocator.free(bytes);
         }
 
         pub fn iterate(self: *const FnProto, index: usize) ?*Node {
             var i = index;
 
-            if (self.lib_name) |lib_name| {
+            if (self.getTrailer("lib_name")) |lib_name| {
                 if (i < 1) return lib_name;
                 i -= 1;
             }
@@ -1066,23 +1164,21 @@ pub const Node = struct {
                 0
             else switch (self.paramsConst()[self.params_len - 1].param_type) {
                 .any_type, .type_expr => self.params_len,
-                .var_args => self.params_len - 1,
             };
             if (i < params_len) {
                 switch (self.paramsConst()[i].param_type) {
                     .any_type => |n| return n,
-                    .var_args => unreachable,
                     .type_expr => |n| return n,
                 }
             }
             i -= params_len;
 
-            if (self.align_expr) |align_expr| {
+            if (self.getTrailer("align_expr")) |align_expr| {
                 if (i < 1) return align_expr;
                 i -= 1;
             }
 
-            if (self.section_expr) |section_expr| {
+            if (self.getTrailer("section_expr")) |section_expr| {
                 if (i < 1) return section_expr;
                 i -= 1;
             }
@@ -1095,7 +1191,7 @@ pub const Node = struct {
                 .Invalid => {},
             }
 
-            if (self.body_node) |body_node| {
+            if (self.getTrailer("body_node")) |body_node| {
                 if (i < 1) return body_node;
                 i -= 1;
             }
@@ -1104,14 +1200,14 @@ pub const Node = struct {
         }
 
         pub fn firstToken(self: *const FnProto) TokenIndex {
-            if (self.visib_token) |visib_token| return visib_token;
-            if (self.extern_export_inline_token) |extern_export_inline_token| return extern_export_inline_token;
-            assert(self.lib_name == null);
+            if (self.getTrailer("visib_token")) |visib_token| return visib_token;
+            if (self.getTrailer("extern_export_inline_token")) |extern_export_inline_token| return extern_export_inline_token;
+            assert(self.getTrailer("lib_name") == null);
             return self.fn_token;
         }
 
         pub fn lastToken(self: *const FnProto) TokenIndex {
-            if (self.body_node) |body_node| return body_node.lastToken();
+            if (self.getTrailer("body_node")) |body_node| return body_node.lastToken();
             switch (self.return_type) {
                 .Explicit, .InferErrorSet => |node| return node.lastToken(),
                 .Invalid => |tok| return tok,
@@ -1119,17 +1215,17 @@ pub const Node = struct {
         }
 
         pub fn params(self: *FnProto) []ParamDecl {
-            const decls_start = @ptrCast([*]u8, self) + @sizeOf(FnProto);
-            return @ptrCast([*]ParamDecl, decls_start)[0..self.params_len];
+            const params_start = @ptrCast([*]u8, self) + @sizeOf(FnProto);
+            return @ptrCast([*]ParamDecl, params_start)[0..self.params_len];
         }
 
         pub fn paramsConst(self: *const FnProto) []const ParamDecl {
-            const decls_start = @ptrCast([*]const u8, self) + @sizeOf(FnProto);
-            return @ptrCast([*]const ParamDecl, decls_start)[0..self.params_len];
+            const params_start = @ptrCast([*]const u8, self) + @sizeOf(FnProto);
+            return @ptrCast([*]const ParamDecl, params_start)[0..self.params_len];
         }
 
-        fn sizeInBytes(params_len: NodeIndex) usize {
-            return @sizeOf(FnProto) + @sizeOf(ParamDecl) * @as(usize, params_len);
+        fn sizeInBytes(params_len: NodeIndex, trailer_flags: TrailerFlags) usize {
+            return @sizeOf(FnProto) + @sizeOf(ParamDecl) * @as(usize, params_len) + trailer_flags.sizeInBytes();
         }
     };
 
@@ -2829,6 +2925,9 @@ pub const Node = struct {
         }
     };
 
+    /// TODO remove from the Node base struct
+    /// TODO actually maybe remove entirely in favor of iterating backward from Node.firstToken()
+    /// and forwards to find same-line doc comments.
     pub const DocComment = struct {
         base: Node = Node{ .id = .DocComment },
         /// Points to the first doc comment token. API users are expected to iterate over the
lib/std/zig/parse.zig
@@ -150,7 +150,7 @@ const Parser = struct {
 
             const visib_token = p.eatToken(.Keyword_pub);
 
-            if (p.parseTopLevelDecl() catch |err| switch (err) {
+            if (p.parseTopLevelDecl(doc_comments, visib_token) catch |err| switch (err) {
                 error.OutOfMemory => return error.OutOfMemory,
                 error.ParseError => {
                     p.findNextContainerMember();
@@ -160,30 +160,7 @@ const Parser = struct {
                 if (field_state == .seen) {
                     field_state = .{ .end = visib_token orelse node.firstToken() };
                 }
-                switch (node.id) {
-                    .FnProto => {
-                        node.cast(Node.FnProto).?.doc_comments = doc_comments;
-                        node.cast(Node.FnProto).?.visib_token = visib_token;
-                    },
-                    .VarDecl => {
-                        node.cast(Node.VarDecl).?.doc_comments = doc_comments;
-                        node.cast(Node.VarDecl).?.visib_token = visib_token;
-                    },
-                    .Use => {
-                        node.cast(Node.Use).?.doc_comments = doc_comments;
-                        node.cast(Node.Use).?.visib_token = visib_token;
-                    },
-                    else => unreachable,
-                }
                 try list.append(node);
-                if (try p.parseAppendedDocComment(node.lastToken())) |appended_comment| {
-                    switch (node.id) {
-                        .FnProto => {},
-                        .VarDecl => node.cast(Node.VarDecl).?.doc_comments = appended_comment,
-                        .Use => node.cast(Node.Use).?.doc_comments = appended_comment,
-                        else => unreachable,
-                    }
-                }
                 continue;
             }
 
@@ -417,7 +394,7 @@ const Parser = struct {
     ///     <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block)
     ///      / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl
     ///      / KEYWORD_usingnamespace Expr SEMICOLON
-    fn parseTopLevelDecl(p: *Parser) !?*Node {
+    fn parseTopLevelDecl(p: *Parser, doc_comments: ?*Node.DocComment, visib_token: ?TokenIndex) !?*Node {
         var lib_name: ?*Node = null;
         const extern_export_inline_token = blk: {
             if (p.eatToken(.Keyword_export)) |token| break :blk token;
@@ -430,20 +407,12 @@ const Parser = struct {
             break :blk null;
         };
 
-        if (try p.parseFnProto()) |node| {
-            const fn_node = node.cast(Node.FnProto).?;
-            fn_node.*.extern_export_inline_token = extern_export_inline_token;
-            fn_node.*.lib_name = lib_name;
-            if (p.eatToken(.Semicolon)) |_| return node;
-
-            if (try p.expectNodeRecoverable(parseBlock, .{
-                // since parseBlock only return error.ParseError on
-                // a missing '}' we can assume this function was
-                // supposed to end here.
-                .ExpectedSemiOrLBrace = .{ .token = p.tok_i },
-            })) |body_node| {
-                fn_node.body_node = body_node;
-            }
+        if (try p.parseFnProto(.top_level, .{
+            .doc_comments = doc_comments,
+            .visib_token = visib_token,
+            .extern_export_inline_token = extern_export_inline_token,
+            .lib_name = lib_name,
+        })) |node| {
             return node;
         }
 
@@ -460,12 +429,13 @@ const Parser = struct {
 
         const thread_local_token = p.eatToken(.Keyword_threadlocal);
 
-        if (try p.parseVarDecl()) |node| {
-            var var_decl = node.cast(Node.VarDecl).?;
-            var_decl.*.thread_local_token = thread_local_token;
-            var_decl.*.comptime_token = null;
-            var_decl.*.extern_export_token = extern_export_inline_token;
-            var_decl.*.lib_name = lib_name;
+        if (try p.parseVarDecl(.{
+            .doc_comments = doc_comments,
+            .visib_token = visib_token,
+            .thread_local_token = thread_local_token,
+            .extern_export_token = extern_export_inline_token,
+            .lib_name = lib_name,
+        })) |node| {
             return node;
         }
 
@@ -485,21 +455,41 @@ const Parser = struct {
             return error.ParseError;
         }
 
-        return p.parseUse();
+        const use_token = p.eatToken(.Keyword_usingnamespace) orelse return null;
+        const expr = try p.expectNode(parseExpr, .{
+            .ExpectedExpr = .{ .token = p.tok_i },
+        });
+        const semicolon_token = try p.expectToken(.Semicolon);
+
+        const node = try p.arena.allocator.create(Node.Use);
+        node.* = .{
+            .doc_comments = doc_comments orelse try p.parseAppendedDocComment(semicolon_token),
+            .visib_token = visib_token,
+            .use_token = use_token,
+            .expr = expr,
+            .semicolon_token = semicolon_token,
+        };
+
+        return &node.base;
     }
 
     /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (Keyword_anytype / TypeExpr)
-    fn parseFnProto(p: *Parser) !?*Node {
+    fn parseFnProto(p: *Parser, level: enum { top_level, as_type }, fields: struct {
+        doc_comments: ?*Node.DocComment = null,
+        visib_token: ?TokenIndex = null,
+        extern_export_inline_token: ?TokenIndex = null,
+        lib_name: ?*Node = null,
+    }) !?*Node {
         // TODO: Remove once extern/async fn rewriting is
-        var is_async = false;
-        var is_extern = false;
+        var is_async: ?void = null;
+        var is_extern_prototype: ?void = null;
         const cc_token: ?TokenIndex = blk: {
             if (p.eatToken(.Keyword_extern)) |token| {
-                is_extern = true;
+                is_extern_prototype = {};
                 break :blk token;
             }
             if (p.eatToken(.Keyword_async)) |token| {
-                is_async = true;
+                is_async = {};
                 break :blk token;
             }
             break :blk null;
@@ -513,6 +503,7 @@ const Parser = struct {
         const lparen = try p.expectToken(.LParen);
         const params = try p.parseParamDeclList();
         defer p.gpa.free(params);
+        const var_args_token = p.eatToken(.Ellipsis3);
         const rparen = try p.expectToken(.RParen);
         const align_expr = try p.parseByteAlign();
         const section_expr = try p.parseLinkSection();
@@ -535,37 +526,53 @@ const Parser = struct {
         else
             R{ .Explicit = return_type_expr.? };
 
-        const var_args_token = if (params.len > 0) blk: {
-            const param_type = params[params.len - 1].param_type;
-            break :blk if (param_type == .var_args) param_type.var_args else null;
-        } else
-            null;
+        const body_node: ?*Node = switch (level) {
+            .top_level => blk: {
+                if (p.eatToken(.Semicolon)) |_| {
+                    break :blk null;
+                }
+                break :blk try p.expectNodeRecoverable(parseBlock, .{
+                    // Since parseBlock only return error.ParseError on
+                    // a missing '}' we can assume this function was
+                    // supposed to end here.
+                    .ExpectedSemiOrLBrace = .{ .token = p.tok_i },
+                });
+            },
+            .as_type => null,
+        };
 
-        const fn_proto_node = try Node.FnProto.alloc(&p.arena.allocator, params.len);
-        fn_proto_node.* = .{
-            .doc_comments = null,
-            .visib_token = null,
-            .fn_token = fn_token,
-            .name_token = name_token,
+        const fn_proto_node = try Node.FnProto.create(&p.arena.allocator, .{
             .params_len = params.len,
+            .fn_token = fn_token,
             .return_type = return_type,
+        }, .{
+            .doc_comments = fields.doc_comments,
+            .visib_token = fields.visib_token,
+            .name_token = name_token,
             .var_args_token = var_args_token,
-            .extern_export_inline_token = null,
-            .body_node = null,
-            .lib_name = null,
+            .extern_export_inline_token = fields.extern_export_inline_token,
+            .body_node = body_node,
+            .lib_name = fields.lib_name,
             .align_expr = align_expr,
             .section_expr = section_expr,
             .callconv_expr = callconv_expr,
-            .is_extern_prototype = is_extern,
+            .is_extern_prototype = is_extern_prototype,
             .is_async = is_async,
-        };
+        });
         std.mem.copy(Node.FnProto.ParamDecl, fn_proto_node.params(), params);
 
         return &fn_proto_node.base;
     }
 
     /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON
-    fn parseVarDecl(p: *Parser) !?*Node {
+    fn parseVarDecl(p: *Parser, fields: struct {
+        doc_comments: ?*Node.DocComment = null,
+        visib_token: ?TokenIndex = null,
+        thread_local_token: ?TokenIndex = null,
+        extern_export_token: ?TokenIndex = null,
+        lib_name: ?*Node = null,
+        comptime_token: ?TokenIndex = null,
+    }) !?*Node {
         const mut_token = p.eatToken(.Keyword_const) orelse
             p.eatToken(.Keyword_var) orelse
             return null;
@@ -587,23 +594,25 @@ const Parser = struct {
         } else null;
         const semicolon_token = try p.expectToken(.Semicolon);
 
-        const node = try p.arena.allocator.create(Node.VarDecl);
-        node.* = .{
-            .doc_comments = null,
-            .visib_token = null,
-            .thread_local_token = null,
+        const doc_comments = fields.doc_comments orelse try p.parseAppendedDocComment(semicolon_token);
+
+        const node = try Node.VarDecl.create(&p.arena.allocator, .{
+            .mut_token = mut_token,
             .name_token = name_token,
+            .semicolon_token = semicolon_token,
+        }, .{
+            .doc_comments = doc_comments,
+            .visib_token = fields.visib_token,
+            .thread_local_token = fields.thread_local_token,
             .eq_token = eq_token,
-            .mut_token = mut_token,
-            .comptime_token = null,
-            .extern_export_token = null,
-            .lib_name = null,
+            .comptime_token = fields.comptime_token,
+            .extern_export_token = fields.extern_export_token,
+            .lib_name = fields.lib_name,
             .type_node = type_node,
             .align_node = align_node,
             .section_node = section_node,
             .init_node = init_node,
-            .semicolon_token = semicolon_token,
-        };
+        });
         return &node.base;
     }
 
@@ -663,10 +672,9 @@ const Parser = struct {
     fn parseStatement(p: *Parser) Error!?*Node {
         const comptime_token = p.eatToken(.Keyword_comptime);
 
-        const var_decl_node = try p.parseVarDecl();
-        if (var_decl_node) |node| {
-            const var_decl = node.cast(Node.VarDecl).?;
-            var_decl.comptime_token = comptime_token;
+        if (try p.parseVarDecl(.{
+            .comptime_token = comptime_token,
+        })) |node| {
             return node;
         }
 
@@ -1527,7 +1535,7 @@ const Parser = struct {
         if (try p.parseAnonLiteral()) |node| return node;
         if (try p.parseErrorSetDecl()) |node| return node;
         if (try p.parseFloatLiteral()) |node| return node;
-        if (try p.parseFnProto()) |node| return node;
+        if (try p.parseFnProto(.as_type, .{})) |node| return node;
         if (try p.parseGroupedExpr()) |node| return node;
         if (try p.parseLabeledTypeExpr()) |node| return node;
         if (try p.parseIdentifier()) |node| return node;
@@ -2028,7 +2036,6 @@ const Parser = struct {
         // TODO cast from tuple to error union is broken
         const P = Node.FnProto.ParamDecl.ParamType;
         if (try p.parseAnyType()) |node| return P{ .any_type = node };
-        if (p.eatToken(.Ellipsis3)) |token| return P{ .var_args = token };
         if (try p.parseTypeExpr()) |node| return P{ .type_expr = node };
         return null;
     }
@@ -3149,21 +3156,6 @@ const Parser = struct {
         return &node.base;
     }
 
-    fn parseUse(p: *Parser) !?*Node {
-        const token = p.eatToken(.Keyword_usingnamespace) orelse return null;
-        const node = try p.arena.allocator.create(Node.Use);
-        node.* = .{
-            .doc_comments = null,
-            .visib_token = null,
-            .use_token = token,
-            .expr = try p.expectNode(parseExpr, .{
-                .ExpectedExpr = .{ .token = p.tok_i },
-            }),
-            .semicolon_token = try p.expectToken(.Semicolon),
-        };
-        return &node.base;
-    }
-
     /// IfPrefix Body (KEYWORD_else Payload? Body)?
     fn parseIf(p: *Parser, bodyParseFn: NodeParseFn) !?*Node {
         const node = (try p.parseIfPrefix()) orelse return null;
lib/std/zig/parser_test.zig
@@ -1,4 +1,32 @@
-const builtin = @import("builtin");
+test "zig fmt: convert var to anytype" {
+    // TODO remove in next release cycle
+    try testTransform(
+        \\pub fn main(
+        \\    a: var,
+        \\    bar: var,
+        \\) void {}
+    ,
+        \\pub fn main(
+        \\    a: anytype,
+        \\    bar: anytype,
+        \\) void {}
+        \\
+    );
+}
+
+test "zig fmt: noasync to nosuspend" {
+    // TODO: remove this
+    try testTransform(
+        \\pub fn main() void {
+        \\    noasync call();
+        \\}
+    ,
+        \\pub fn main() void {
+        \\    nosuspend call();
+        \\}
+        \\
+    );
+}
 
 test "recovery: top level" {
     try testError(
@@ -3146,20 +3174,6 @@ test "zig fmt: hexadeciaml float literals with underscore separators" {
     );
 }
 
-test "zig fmt: noasync to nosuspend" {
-    // TODO: remove this
-    try testTransform(
-        \\pub fn main() void {
-        \\    noasync call();
-        \\}
-    ,
-        \\pub fn main() void {
-        \\    nosuspend call();
-        \\}
-        \\
-    );
-}
-
 test "zig fmt: convert async fn into callconv(.Async)" {
     try testTransform(
         \\async fn foo() void {}
@@ -3180,18 +3194,9 @@ test "zig fmt: convert extern fn proto into callconv(.C)" {
     );
 }
 
-test "zig fmt: convert var to anytype" {
-    // TODO remove in next release cycle
-    try testTransform(
-        \\pub fn main(
-        \\    a: var,
-        \\    bar: var,
-        \\) void {}
-    ,
-        \\pub fn main(
-        \\    a: anytype,
-        \\    bar: anytype,
-        \\) void {}
+test "zig fmt: C var args" {
+    try testCanonical(
+        \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
         \\
     );
 }
lib/std/zig/render.zig
@@ -227,9 +227,9 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
         .FnProto => {
             const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
 
-            try renderDocComments(tree, stream, fn_proto, indent, start_col);
+            try renderDocComments(tree, stream, fn_proto, fn_proto.getTrailer("doc_comments"), indent, start_col);
 
-            if (fn_proto.body_node) |body_node| {
+            if (fn_proto.getTrailer("body_node")) |body_node| {
                 try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space);
                 try renderExpression(allocator, stream, tree, indent, start_col, body_node, space);
             } else {
@@ -252,14 +252,14 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
         .VarDecl => {
             const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl);
 
-            try renderDocComments(tree, stream, var_decl, indent, start_col);
+            try renderDocComments(tree, stream, var_decl, var_decl.getTrailer("doc_comments"), indent, start_col);
             try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl);
         },
 
         .TestDecl => {
             const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl);
 
-            try renderDocComments(tree, stream, test_decl, indent, start_col);
+            try renderDocComments(tree, stream, test_decl, test_decl.doc_comments, indent, start_col);
             try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space);
             try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space);
             try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space);
@@ -268,7 +268,7 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr
         .ContainerField => {
             const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl);
 
-            try renderDocComments(tree, stream, field, indent, start_col);
+            try renderDocComments(tree, stream, field, field.doc_comments, indent, start_col);
             if (field.comptime_token) |t| {
                 try renderToken(tree, stream, t, indent, start_col, .Space); // comptime
             }
@@ -1409,7 +1409,7 @@ fn renderExpression(
         .ErrorTag => {
             const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base);
 
-            try renderDocComments(tree, stream, tag, indent, start_col);
+            try renderDocComments(tree, stream, tag, tag.doc_comments, indent, start_col);
             return renderToken(tree, stream, tag.name_token, indent, start_col, space); // name
         },
 
@@ -1483,23 +1483,23 @@ fn renderExpression(
         .FnProto => {
             const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", base);
 
-            if (fn_proto.visib_token) |visib_token_index| {
+            if (fn_proto.getTrailer("visib_token")) |visib_token_index| {
                 const visib_token = tree.token_ids[visib_token_index];
                 assert(visib_token == .Keyword_pub or visib_token == .Keyword_export);
 
                 try renderToken(tree, stream, visib_token_index, indent, start_col, Space.Space); // pub
             }
 
-            if (fn_proto.extern_export_inline_token) |extern_export_inline_token| {
-                if (!fn_proto.is_extern_prototype)
+            if (fn_proto.getTrailer("extern_export_inline_token")) |extern_export_inline_token| {
+                if (fn_proto.getTrailer("is_extern_prototype") == null)
                     try renderToken(tree, stream, extern_export_inline_token, indent, start_col, Space.Space); // extern/export/inline
             }
 
-            if (fn_proto.lib_name) |lib_name| {
+            if (fn_proto.getTrailer("lib_name")) |lib_name| {
                 try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space);
             }
 
-            const lparen = if (fn_proto.name_token) |name_token| blk: {
+            const lparen = if (fn_proto.getTrailer("name_token")) |name_token| blk: {
                 try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn
                 try renderToken(tree, stream, name_token, indent, start_col, Space.None); // name
                 break :blk tree.nextToken(name_token);
@@ -1512,11 +1512,11 @@ fn renderExpression(
             const rparen = tree.prevToken(
             // the first token for the annotation expressions is the left
             // parenthesis, hence the need for two prevToken
-            if (fn_proto.align_expr) |align_expr|
+            if (fn_proto.getTrailer("align_expr")) |align_expr|
                 tree.prevToken(tree.prevToken(align_expr.firstToken()))
-            else if (fn_proto.section_expr) |section_expr|
+            else if (fn_proto.getTrailer("section_expr")) |section_expr|
                 tree.prevToken(tree.prevToken(section_expr.firstToken()))
-            else if (fn_proto.callconv_expr) |callconv_expr|
+            else if (fn_proto.getTrailer("callconv_expr")) |callconv_expr|
                 tree.prevToken(tree.prevToken(callconv_expr.firstToken()))
             else switch (fn_proto.return_type) {
                 .Explicit => |node| node.firstToken(),
@@ -1537,11 +1537,14 @@ fn renderExpression(
                 for (fn_proto.params()) |param_decl, i| {
                     try renderParamDecl(allocator, stream, tree, indent, start_col, param_decl, Space.None);
 
-                    if (i + 1 < fn_proto.params_len) {
+                    if (i + 1 < fn_proto.params_len or fn_proto.getTrailer("var_args_token") != null) {
                         const comma = tree.nextToken(param_decl.lastToken());
                         try renderToken(tree, stream, comma, indent, start_col, Space.Space); // ,
                     }
                 }
+                if (fn_proto.getTrailer("var_args_token")) |var_args_token| {
+                    try renderToken(tree, stream, var_args_token, indent, start_col, Space.None);
+                }
             } else {
                 // one param per line
                 const new_indent = indent + indent_delta;
@@ -1551,12 +1554,16 @@ fn renderExpression(
                     try stream.writeByteNTimes(' ', new_indent);
                     try renderParamDecl(allocator, stream, tree, new_indent, start_col, param_decl, Space.Comma);
                 }
+                if (fn_proto.getTrailer("var_args_token")) |var_args_token| {
+                    try stream.writeByteNTimes(' ', new_indent);
+                    try renderToken(tree, stream, var_args_token, new_indent, start_col, Space.Comma);
+                }
                 try stream.writeByteNTimes(' ', indent);
             }
 
             try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // )
 
-            if (fn_proto.align_expr) |align_expr| {
+            if (fn_proto.getTrailer("align_expr")) |align_expr| {
                 const align_rparen = tree.nextToken(align_expr.lastToken());
                 const align_lparen = tree.prevToken(align_expr.firstToken());
                 const align_kw = tree.prevToken(align_lparen);
@@ -1567,7 +1574,7 @@ fn renderExpression(
                 try renderToken(tree, stream, align_rparen, indent, start_col, Space.Space); // )
             }
 
-            if (fn_proto.section_expr) |section_expr| {
+            if (fn_proto.getTrailer("section_expr")) |section_expr| {
                 const section_rparen = tree.nextToken(section_expr.lastToken());
                 const section_lparen = tree.prevToken(section_expr.firstToken());
                 const section_kw = tree.prevToken(section_lparen);
@@ -1578,7 +1585,7 @@ fn renderExpression(
                 try renderToken(tree, stream, section_rparen, indent, start_col, Space.Space); // )
             }
 
-            if (fn_proto.callconv_expr) |callconv_expr| {
+            if (fn_proto.getTrailer("callconv_expr")) |callconv_expr| {
                 const callconv_rparen = tree.nextToken(callconv_expr.lastToken());
                 const callconv_lparen = tree.prevToken(callconv_expr.firstToken());
                 const callconv_kw = tree.prevToken(callconv_lparen);
@@ -1587,9 +1594,9 @@ fn renderExpression(
                 try renderToken(tree, stream, callconv_lparen, indent, start_col, Space.None); // (
                 try renderExpression(allocator, stream, tree, indent, start_col, callconv_expr, Space.None);
                 try renderToken(tree, stream, callconv_rparen, indent, start_col, Space.Space); // )
-            } else if (fn_proto.is_extern_prototype) {
+            } else if (fn_proto.getTrailer("is_extern_prototype") != null) {
                 try stream.writeAll("callconv(.C) ");
-            } else if (fn_proto.is_async) {
+            } else if (fn_proto.getTrailer("is_async") != null) {
                 try stream.writeAll("callconv(.Async) ");
             }
 
@@ -2177,64 +2184,69 @@ fn renderVarDecl(
     start_col: *usize,
     var_decl: *ast.Node.VarDecl,
 ) (@TypeOf(stream).Error || Error)!void {
-    if (var_decl.visib_token) |visib_token| {
+    if (var_decl.getTrailer("visib_token")) |visib_token| {
         try renderToken(tree, stream, visib_token, indent, start_col, Space.Space); // pub
     }
 
-    if (var_decl.extern_export_token) |extern_export_token| {
+    if (var_decl.getTrailer("extern_export_token")) |extern_export_token| {
         try renderToken(tree, stream, extern_export_token, indent, start_col, Space.Space); // extern
 
-        if (var_decl.lib_name) |lib_name| {
+        if (var_decl.getTrailer("lib_name")) |lib_name| {
             try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space); // "lib"
         }
     }
 
-    if (var_decl.comptime_token) |comptime_token| {
+    if (var_decl.getTrailer("comptime_token")) |comptime_token| {
         try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space); // comptime
     }
 
-    if (var_decl.thread_local_token) |thread_local_token| {
+    if (var_decl.getTrailer("thread_local_token")) |thread_local_token| {
         try renderToken(tree, stream, thread_local_token, indent, start_col, Space.Space); // threadlocal
     }
     try renderToken(tree, stream, var_decl.mut_token, indent, start_col, Space.Space); // var
 
-    const name_space = if (var_decl.type_node == null and (var_decl.align_node != null or
-        var_decl.section_node != null or var_decl.init_node != null)) Space.Space else Space.None;
+    const name_space = if (var_decl.getTrailer("type_node") == null and
+        (var_decl.getTrailer("align_node") != null or
+        var_decl.getTrailer("section_node") != null or
+        var_decl.getTrailer("init_node") != null))
+        Space.Space
+    else
+        Space.None;
     try renderToken(tree, stream, var_decl.name_token, indent, start_col, name_space);
 
-    if (var_decl.type_node) |type_node| {
+    if (var_decl.getTrailer("type_node")) |type_node| {
         try renderToken(tree, stream, tree.nextToken(var_decl.name_token), indent, start_col, Space.Space);
-        const s = if (var_decl.align_node != null or
-            var_decl.section_node != null or
-            var_decl.init_node != null) Space.Space else Space.None;
+        const s = if (var_decl.getTrailer("align_node") != null or
+            var_decl.getTrailer("section_node") != null or
+            var_decl.getTrailer("init_node") != null) Space.Space else Space.None;
         try renderExpression(allocator, stream, tree, indent, start_col, type_node, s);
     }
 
-    if (var_decl.align_node) |align_node| {
+    if (var_decl.getTrailer("align_node")) |align_node| {
         const lparen = tree.prevToken(align_node.firstToken());
         const align_kw = tree.prevToken(lparen);
         const rparen = tree.nextToken(align_node.lastToken());
         try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align
         try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
         try renderExpression(allocator, stream, tree, indent, start_col, align_node, Space.None);
-        const s = if (var_decl.section_node != null or var_decl.init_node != null) Space.Space else Space.None;
+        const s = if (var_decl.getTrailer("section_node") != null or var_decl.getTrailer("init_node") != null) Space.Space else Space.None;
         try renderToken(tree, stream, rparen, indent, start_col, s); // )
     }
 
-    if (var_decl.section_node) |section_node| {
+    if (var_decl.getTrailer("section_node")) |section_node| {
         const lparen = tree.prevToken(section_node.firstToken());
         const section_kw = tree.prevToken(lparen);
         const rparen = tree.nextToken(section_node.lastToken());
         try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // linksection
         try renderToken(tree, stream, lparen, indent, start_col, Space.None); // (
         try renderExpression(allocator, stream, tree, indent, start_col, section_node, Space.None);
-        const s = if (var_decl.init_node != null) Space.Space else Space.None;
+        const s = if (var_decl.getTrailer("init_node") != null) Space.Space else Space.None;
         try renderToken(tree, stream, rparen, indent, start_col, s); // )
     }
 
-    if (var_decl.init_node) |init_node| {
+    if (var_decl.getTrailer("init_node")) |init_node| {
         const s = if (init_node.id == .MultilineStringLiteral) Space.None else Space.Space;
-        try renderToken(tree, stream, var_decl.eq_token.?, indent, start_col, s); // =
+        try renderToken(tree, stream, var_decl.getTrailer("eq_token").?, indent, start_col, s); // =
         try renderExpression(allocator, stream, tree, indent, start_col, init_node, Space.None);
     }
 
@@ -2250,7 +2262,7 @@ fn renderParamDecl(
     param_decl: ast.Node.FnProto.ParamDecl,
     space: Space,
 ) (@TypeOf(stream).Error || Error)!void {
-    try renderDocComments(tree, stream, param_decl, indent, start_col);
+    try renderDocComments(tree, stream, param_decl, param_decl.doc_comments, indent, start_col);
 
     if (param_decl.comptime_token) |comptime_token| {
         try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space);
@@ -2263,7 +2275,6 @@ fn renderParamDecl(
         try renderToken(tree, stream, tree.nextToken(name_token), indent, start_col, Space.Space); // :
     }
     switch (param_decl.param_type) {
-        .var_args => |token| try renderToken(tree, stream, token, indent, start_col, space),
         .any_type, .type_expr => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, space),
     }
 }
@@ -2519,10 +2530,11 @@ fn renderDocComments(
     tree: *ast.Tree,
     stream: anytype,
     node: anytype,
+    doc_comments: ?*ast.Node.DocComment,
     indent: usize,
     start_col: *usize,
 ) (@TypeOf(stream).Error || Error)!void {
-    const comment = node.doc_comments orelse return;
+    const comment = doc_comments orelse return;
     return renderDocCommentsToken(tree, stream, comment, node.firstToken(), indent, start_col);
 }
 
src-self-hosted/Module.zig
@@ -1130,7 +1130,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
             };
             defer fn_type_scope.instructions.deinit(self.gpa);
 
-            const body_node = fn_proto.body_node orelse
+            const body_node = fn_proto.getTrailer("body_node") orelse
                 return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{});
 
             const param_decls = fn_proto.params();
@@ -1138,21 +1138,23 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
             for (param_decls) |param_decl, i| {
                 const param_type_node = switch (param_decl.param_type) {
                     .any_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}),
-                    .var_args => |tok| return self.failTok(&fn_type_scope.base, tok, "TODO implement var args", .{}),
                     .type_expr => |node| node,
                 };
                 param_types[i] = try self.astGenExpr(&fn_type_scope.base, param_type_node);
             }
-            if (fn_proto.lib_name) |lib_name| {
+            if (fn_proto.getTrailer("var_args_token")) |var_args_token| {
+                return self.failTok(&fn_type_scope.base, var_args_token, "TODO implement var args", .{});
+            }
+            if (fn_proto.getTrailer("lib_name")) |lib_name| {
                 return self.failNode(&fn_type_scope.base, lib_name, "TODO implement function library name", .{});
             }
-            if (fn_proto.align_expr) |align_expr| {
+            if (fn_proto.getTrailer("align_expr")) |align_expr| {
                 return self.failNode(&fn_type_scope.base, align_expr, "TODO implement function align expression", .{});
             }
-            if (fn_proto.section_expr) |sect_expr| {
+            if (fn_proto.getTrailer("section_expr")) |sect_expr| {
                 return self.failNode(&fn_type_scope.base, sect_expr, "TODO implement function section expression", .{});
             }
-            if (fn_proto.callconv_expr) |callconv_expr| {
+            if (fn_proto.getTrailer("callconv_expr")) |callconv_expr| {
                 return self.failNode(
                     &fn_type_scope.base,
                     callconv_expr,
@@ -1265,10 +1267,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
                 self.bin_file.freeDecl(decl);
             }
 
-            if (fn_proto.extern_export_inline_token) |maybe_export_token| {
+            if (fn_proto.getTrailer("extern_export_inline_token")) |maybe_export_token| {
                 if (tree.token_ids[maybe_export_token] == .Keyword_export) {
                     const export_src = tree.token_locs[maybe_export_token].start;
-                    const name_loc = tree.token_locs[fn_proto.name_token.?];
+                    const name_loc = tree.token_locs[fn_proto.getTrailer("name_token").?];
                     const name = tree.tokenSliceLoc(name_loc);
                     // The scope needs to have the decl in it.
                     try self.analyzeExport(&block_scope.base, export_src, name, decl);
@@ -1867,7 +1869,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
     for (decls) |src_decl, decl_i| {
         if (src_decl.cast(ast.Node.FnProto)) |fn_proto| {
             // We will create a Decl for it regardless of analysis status.
-            const name_tok = fn_proto.name_token orelse {
+            const name_tok = fn_proto.getTrailer("name_token") orelse {
                 @panic("TODO missing function name");
             };
 
@@ -1893,7 +1895,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
             } else {
                 const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
                 root_scope.decls.appendAssumeCapacity(new_decl);
-                if (fn_proto.extern_export_inline_token) |maybe_export_token| {
+                if (fn_proto.getTrailer("extern_export_inline_token")) |maybe_export_token| {
                     if (tree.token_ids[maybe_export_token] == .Keyword_export) {
                         self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
                     }
src-self-hosted/translate_c.zig
@@ -586,11 +586,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
     for (proto_node.params()) |*param, i| {
         const param_name = if (param.name_token) |name_tok|
             tokenSlice(c, name_tok)
-        else if (param.param_type == .var_args) {
-            assert(i + 1 == proto_node.params_len);
-            proto_node.params_len -= 1;
-            break;
-        } else
+        else
             return failDecl(c, fn_decl_loc, fn_name, "function {} parameter has no name", .{fn_name});
 
         const c_param = ZigClangFunctionDecl_getParamDecl(fn_decl, param_id);
@@ -602,10 +598,20 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
         if (!is_const) {
             const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{}", .{mangled_param_name});
             const arg_name = try block_scope.makeMangledName(c, bare_arg_name);
-            const node = try transCreateNodeVarDecl(c, false, false, mangled_param_name);
-            node.eq_token = try appendToken(c, .Equal, "=");
-            node.init_node = try transCreateNodeIdentifier(c, arg_name);
-            node.semicolon_token = try appendToken(c, .Semicolon, ";");
+
+            const mut_tok = try appendToken(c, .Keyword_var, "var");
+            const name_tok = try appendIdentifier(c, mangled_param_name);
+            const eq_token = try appendToken(c, .Equal, "=");
+            const init_node = try transCreateNodeIdentifier(c, arg_name);
+            const semicolon_token = try appendToken(c, .Semicolon, ";");
+            const node = try ast.Node.VarDecl.create(c.arena, .{
+                .mut_token = mut_tok,
+                .name_token = name_tok,
+                .semicolon_token = semicolon_token,
+            }, .{
+                .eq_token = eq_token,
+                .init_node = init_node,
+            });
             try block_scope.statements.append(&node.base);
             param.name_token = try appendIdentifier(c, arg_name);
             _ = try appendToken(c, .Colon, ":");
@@ -622,7 +628,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
         => return failDecl(c, fn_decl_loc, fn_name, "unable to translate function", .{}),
     };
     const body_node = try block_scope.complete(rp.c);
-    proto_node.body_node = &body_node.base;
+    proto_node.setTrailer("body_node", &body_node.base);
     return addTopLevelDecl(c, fn_name, &proto_node.base);
 }
 
@@ -725,23 +731,20 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void {
         break :blk null;
     };
 
-    const node = try c.arena.create(ast.Node.VarDecl);
-    node.* = .{
-        .doc_comments = null,
+    const node = try ast.Node.VarDecl.create(c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = try appendToken(c, .Semicolon, ";"),
+    }, .{
         .visib_token = visib_tok,
         .thread_local_token = thread_local_token,
-        .name_token = name_tok,
         .eq_token = eq_tok,
-        .mut_token = mut_tok,
-        .comptime_token = null,
         .extern_export_token = extern_tok,
-        .lib_name = null,
         .type_node = type_node,
         .align_node = align_expr,
         .section_node = linksection_expr,
         .init_node = init_node,
-        .semicolon_token = try appendToken(c, .Semicolon, ";"),
-    };
+    });
     return addTopLevelDecl(c, checked_name, &node.base);
 }
 
@@ -795,26 +798,41 @@ fn transTypeDef(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl, top_l
 
     _ = try c.decl_table.put(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)), checked_name);
     const node = (try transCreateNodeTypedef(rp, typedef_decl, true, checked_name)) orelse return null;
-    try addTopLevelDecl(c, checked_name, &node.base);
+    try addTopLevelDecl(c, checked_name, node);
     return transCreateNodeIdentifier(c, checked_name);
 }
 
-fn transCreateNodeTypedef(rp: RestorePoint, typedef_decl: *const ZigClangTypedefNameDecl, toplevel: bool, checked_name: []const u8) Error!?*ast.Node.VarDecl {
-    const node = try transCreateNodeVarDecl(rp.c, toplevel, true, checked_name);
-    node.eq_token = try appendToken(rp.c, .Equal, "=");
-
+fn transCreateNodeTypedef(
+    rp: RestorePoint,
+    typedef_decl: *const ZigClangTypedefNameDecl,
+    toplevel: bool,
+    checked_name: []const u8,
+) Error!?*ast.Node {
+    const visib_tok = if (toplevel) try appendToken(rp.c, .Keyword_pub, "pub") else null;
+    const mut_tok = try appendToken(rp.c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(rp.c, checked_name);
+    const eq_token = try appendToken(rp.c, .Equal, "=");
     const child_qt = ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl);
     const typedef_loc = ZigClangTypedefNameDecl_getLocation(typedef_decl);
-    node.init_node = transQualType(rp, child_qt, typedef_loc) catch |err| switch (err) {
+    const init_node = transQualType(rp, child_qt, typedef_loc) catch |err| switch (err) {
         error.UnsupportedType => {
             try failDecl(rp.c, typedef_loc, checked_name, "unable to resolve typedef child type", .{});
             return null;
         },
         error.OutOfMemory => |e| return e,
     };
+    const semicolon_token = try appendToken(rp.c, .Semicolon, ";");
 
-    node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
-    return node;
+    const node = try ast.Node.VarDecl.create(rp.c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .visib_token = visib_tok,
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
+    return &node.base;
 }
 
 fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?*ast.Node {
@@ -847,12 +865,14 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?*
     const name = try std.fmt.allocPrint(c.arena, "{}_{}", .{ container_kind_name, bare_name });
     _ = try c.decl_table.put(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)), name);
 
-    const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name);
+    const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null;
+    const mut_tok = try appendToken(c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(c, name);
 
-    node.eq_token = try appendToken(c, .Equal, "=");
+    const eq_token = try appendToken(c, .Equal, "=");
 
     var semicolon: ast.TokenIndex = undefined;
-    node.init_node = blk: {
+    const init_node = blk: {
         const rp = makeRestorePoint(c);
         const record_def = ZigClangRecordDecl_getDefinition(record_decl) orelse {
             const opaque = try transCreateNodeOpaqueType(c);
@@ -959,7 +979,16 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?*
         semicolon = try appendToken(c, .Semicolon, ";");
         break :blk &container_node.base;
     };
-    node.semicolon_token = semicolon;
+
+    const node = try ast.Node.VarDecl.create(c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon,
+    }, .{
+        .visib_token = visib_tok,
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
 
     try addTopLevelDecl(c, name, &node.base);
     if (!is_unnamed)
@@ -982,10 +1011,13 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No
 
     const name = try std.fmt.allocPrint(c.arena, "enum_{}", .{bare_name});
     _ = try c.decl_table.put(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)), name);
-    const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name);
-    node.eq_token = try appendToken(c, .Equal, "=");
 
-    node.init_node = if (ZigClangEnumDecl_getDefinition(enum_decl)) |enum_def| blk: {
+    const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null;
+    const mut_tok = try appendToken(c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(c, name);
+    const eq_token = try appendToken(c, .Equal, "=");
+
+    const init_node = if (ZigClangEnumDecl_getDefinition(enum_decl)) |enum_def| blk: {
         var pure_enum = true;
         var it = ZigClangEnumDecl_enumerator_begin(enum_def);
         var end_it = ZigClangEnumDecl_enumerator_end(enum_def);
@@ -1063,8 +1095,10 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No
 
             // In C each enum value is in the global namespace. So we put them there too.
             // At this point we can rely on the enum emitting successfully.
-            const tld_node = try transCreateNodeVarDecl(c, true, true, enum_val_name);
-            tld_node.eq_token = try appendToken(c, .Equal, "=");
+            const tld_visib_tok = try appendToken(c, .Keyword_pub, "pub");
+            const tld_mut_tok = try appendToken(c, .Keyword_const, "const");
+            const tld_name_tok = try appendIdentifier(c, enum_val_name);
+            const tld_eq_token = try appendToken(c, .Equal, "=");
             const cast_node = try rp.c.createBuiltinCall("@enumToInt", 1);
             const enum_ident = try transCreateNodeIdentifier(c, name);
             const period_tok = try appendToken(c, .Period, ".");
@@ -1078,8 +1112,17 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No
             };
             cast_node.params()[0] = &field_access_node.base;
             cast_node.rparen_token = try appendToken(rp.c, .RParen, ")");
-            tld_node.init_node = &cast_node.base;
-            tld_node.semicolon_token = try appendToken(c, .Semicolon, ";");
+            const tld_init_node = &cast_node.base;
+            const tld_semicolon_token = try appendToken(c, .Semicolon, ";");
+            const tld_node = try ast.Node.VarDecl.create(c.arena, .{
+                .name_token = tld_name_tok,
+                .mut_token = tld_mut_tok,
+                .semicolon_token = tld_semicolon_token,
+            }, .{
+                .visib_token = tld_visib_tok,
+                .eq_token = tld_eq_token,
+                .init_node = tld_init_node,
+            });
             try addTopLevelDecl(c, field_name, &tld_node.base);
         }
         // make non exhaustive
@@ -1109,7 +1152,16 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No
     } else
         try transCreateNodeOpaqueType(c);
 
-    node.semicolon_token = try appendToken(c, .Semicolon, ";");
+    const semicolon_token = try appendToken(c, .Semicolon, ";");
+    const node = try ast.Node.VarDecl.create(c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .visib_token = visib_tok,
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
 
     try addTopLevelDecl(c, name, &node.base);
     if (!is_unnamed)
@@ -1118,10 +1170,22 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No
 }
 
 fn createAlias(c: *Context, alias: anytype) !void {
-    const node = try transCreateNodeVarDecl(c, true, true, alias.alias);
-    node.eq_token = try appendToken(c, .Equal, "=");
-    node.init_node = try transCreateNodeIdentifier(c, alias.name);
-    node.semicolon_token = try appendToken(c, .Semicolon, ";");
+    const visib_tok = try appendToken(c, .Keyword_pub, "pub");
+    const mut_tok = try appendToken(c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(c, alias.alias);
+    const eq_token = try appendToken(c, .Equal, "=");
+    const init_node = try transCreateNodeIdentifier(c, alias.name);
+    const semicolon_token = try appendToken(c, .Semicolon, ";");
+
+    const node = try ast.Node.VarDecl.create(c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .visib_token = visib_tok,
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
     return addTopLevelDecl(c, alias.alias, &node.base);
 }
 
@@ -1461,13 +1525,17 @@ fn transDeclStmtOne(
                 @ptrCast(*const ZigClangNamedDecl, var_decl),
             ));
             const mangled_name = try block_scope.makeMangledName(c, name);
-            const node = try transCreateNodeVarDecl(c, false, ZigClangQualType_isConstQualified(qual_type), mangled_name);
+            const mut_tok = if (ZigClangQualType_isConstQualified(qual_type))
+                try appendToken(c, .Keyword_const, "const")
+            else
+                try appendToken(c, .Keyword_var, "var");
+            const name_tok = try appendIdentifier(c, mangled_name);
 
             _ = try appendToken(c, .Colon, ":");
             const loc = ZigClangDecl_getLocation(decl);
-            node.type_node = try transQualType(rp, qual_type, loc);
+            const type_node = try transQualType(rp, qual_type, loc);
 
-            node.eq_token = try appendToken(c, .Equal, "=");
+            const eq_token = try appendToken(c, .Equal, "=");
             var init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr|
                 try transExprCoercing(rp, scope, expr, .used, .r_value)
             else
@@ -1478,8 +1546,17 @@ fn transDeclStmtOne(
                 builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
                 init_node = &builtin_node.base;
             }
-            node.init_node = init_node;
-            node.semicolon_token = try appendToken(c, .Semicolon, ";");
+            const semicolon_token = try appendToken(c, .Semicolon, ";");
+            const node = try ast.Node.VarDecl.create(c.arena, .{
+                .name_token = name_tok,
+                .mut_token = mut_tok,
+                .semicolon_token = semicolon_token,
+            }, .{
+                .thread_local_token = thread_local_token,
+                .eq_token = eq_token,
+                .type_node = type_node,
+                .init_node = init_node,
+            });
             return &node.base;
         },
         .Typedef => {
@@ -1494,7 +1571,7 @@ fn transDeclStmtOne(
             const mangled_name = try block_scope.makeMangledName(c, name);
             const node = (try transCreateNodeTypedef(rp, typedef_decl, false, mangled_name)) orelse
                 return error.UnsupportedTranslation;
-            return &node.base;
+            return node;
         },
         else => |kind| return revertAndWarn(
             rp,
@@ -3105,12 +3182,21 @@ fn transCreatePreCrement(
     defer block_scope.deinit();
     const ref = try block_scope.makeMangledName(rp.c, "ref");
 
-    const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
-    node.eq_token = try appendToken(rp.c, .Equal, "=");
+    const mut_tok = try appendToken(rp.c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(rp.c, ref);
+    const eq_token = try appendToken(rp.c, .Equal, "=");
     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, ";");
+    const init_node = &rhs_node.base;
+    const semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const node = try ast.Node.VarDecl.create(rp.c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
     try block_scope.statements.append(&node.base);
 
     const lhs_node = try transCreateNodeIdentifier(rp.c, ref);
@@ -3171,12 +3257,21 @@ fn transCreatePostCrement(
     defer block_scope.deinit();
     const ref = try block_scope.makeMangledName(rp.c, "ref");
 
-    const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
-    node.eq_token = try appendToken(rp.c, .Equal, "=");
+    const mut_tok = try appendToken(rp.c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(rp.c, ref);
+    const eq_token = try appendToken(rp.c, .Equal, "=");
     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, ";");
+    const init_node = &rhs_node.base;
+    const semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const node = try ast.Node.VarDecl.create(rp.c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
     try block_scope.statements.append(&node.base);
 
     const lhs_node = try transCreateNodeIdentifier(rp.c, ref);
@@ -3184,10 +3279,19 @@ fn transCreatePostCrement(
     _ = try appendToken(rp.c, .Semicolon, ";");
 
     const tmp = try block_scope.makeMangledName(rp.c, "tmp");
-    const tmp_node = try transCreateNodeVarDecl(rp.c, false, true, tmp);
-    tmp_node.eq_token = try appendToken(rp.c, .Equal, "=");
-    tmp_node.init_node = ref_node;
-    tmp_node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const tmp_mut_tok = try appendToken(rp.c, .Keyword_const, "const");
+    const tmp_name_tok = try appendIdentifier(rp.c, tmp);
+    const tmp_eq_token = try appendToken(rp.c, .Equal, "=");
+    const tmp_init_node = ref_node;
+    const tmp_semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const tmp_node = try ast.Node.VarDecl.create(rp.c.arena, .{
+        .name_token = tmp_name_tok,
+        .mut_token = tmp_mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .eq_token = tmp_eq_token,
+        .init_node = tmp_init_node,
+    });
     try block_scope.statements.append(&tmp_node.base);
 
     const token = try appendToken(rp.c, op_tok_id, bytes);
@@ -3325,12 +3429,21 @@ fn transCreateCompoundAssign(
     defer block_scope.deinit();
     const ref = try block_scope.makeMangledName(rp.c, "ref");
 
-    const node = try transCreateNodeVarDecl(rp.c, false, true, ref);
-    node.eq_token = try appendToken(rp.c, .Equal, "=");
+    const mut_tok = try appendToken(rp.c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(rp.c, ref);
+    const eq_token = try appendToken(rp.c, .Equal, "=");
     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, ";");
+    const init_node = &addr_node.base;
+    const semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const node = try ast.Node.VarDecl.create(rp.c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
     try block_scope.statements.append(&node.base);
 
     const lhs_node = try transCreateNodeIdentifier(rp.c, ref);
@@ -3375,8 +3488,8 @@ fn transCreateCompoundAssign(
         const rhs_bin = try transCreateNodeInfixOp(rp, scope, ref_node, bin_op, bin_token, rhs_node, .used, false);
         _ = try appendToken(rp.c, .Semicolon, ";");
 
-        const eq_token = try appendToken(rp.c, .Equal, "=");
-        const assign = try transCreateNodeInfixOp(rp, scope, ref_node, .Assign, eq_token, rhs_bin, .used, false);
+        const ass_eq_token = try appendToken(rp.c, .Equal, "=");
+        const assign = try transCreateNodeInfixOp(rp, scope, ref_node, .Assign, ass_eq_token, rhs_bin, .used, false);
         try block_scope.statements.append(assign);
     }
 
@@ -3494,10 +3607,19 @@ fn transBinaryConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const
     defer block_scope.deinit();
 
     const mangled_name = try block_scope.makeMangledName(rp.c, "cond_temp");
-    const tmp_var = try transCreateNodeVarDecl(rp.c, false, true, mangled_name);
-    tmp_var.eq_token = try appendToken(rp.c, .Equal, "=");
-    tmp_var.init_node = try transExpr(rp, &block_scope.base, cond_expr, .used, .r_value);
-    tmp_var.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const mut_tok = try appendToken(rp.c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(rp.c, mangled_name);
+    const eq_token = try appendToken(rp.c, .Equal, "=");
+    const init_node = try transExpr(rp, &block_scope.base, cond_expr, .used, .r_value);
+    const semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const tmp_var = try ast.Node.VarDecl.create(rp.c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
     try block_scope.statements.append(&tmp_var.base);
 
     const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label);
@@ -3932,9 +4054,9 @@ fn transCreateNodeAssign(
     defer block_scope.deinit();
 
     const tmp = try block_scope.makeMangledName(rp.c, "tmp");
-
-    const node = try transCreateNodeVarDecl(rp.c, false, true, tmp);
-    node.eq_token = try appendToken(rp.c, .Equal, "=");
+    const mut_tok = try appendToken(rp.c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(rp.c, tmp);
+    const eq_token = try appendToken(rp.c, .Equal, "=");
     var rhs_node = try transExpr(rp, &block_scope.base, rhs, .used, .r_value);
     if (!exprIsBooleanType(lhs) and isBoolRes(rhs_node)) {
         const builtin_node = try rp.c.createBuiltinCall("@boolToInt", 1);
@@ -3942,16 +4064,24 @@ fn transCreateNodeAssign(
         builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")");
         rhs_node = &builtin_node.base;
     }
-    node.init_node = rhs_node;
-    node.semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const init_node = rhs_node;
+    const semicolon_token = try appendToken(rp.c, .Semicolon, ";");
+    const node = try ast.Node.VarDecl.create(rp.c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
     try block_scope.statements.append(&node.base);
 
     const lhs_node = try transExpr(rp, &block_scope.base, lhs, .used, .l_value);
-    const eq_token = try appendToken(rp.c, .Equal, "=");
+    const lhs_eq_token = try appendToken(rp.c, .Equal, "=");
     const ident = try transCreateNodeIdentifier(rp.c, tmp);
     _ = try appendToken(rp.c, .Semicolon, ";");
 
-    const assign = try transCreateNodeInfixOp(rp, &block_scope.base, lhs_node, .Assign, eq_token, ident, .used, false);
+    const assign = try transCreateNodeInfixOp(rp, &block_scope.base, lhs_node, .Assign, lhs_eq_token, ident, .used, false);
     try block_scope.statements.append(assign);
 
     const break_node = try transCreateNodeBreak(rp.c, label_name);
@@ -4232,28 +4362,10 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a
 
     _ = try appendToken(c, .RParen, ")");
 
-    const fn_proto = try ast.Node.FnProto.alloc(c.arena, fn_params.items.len);
-    fn_proto.* = .{
-        .doc_comments = null,
-        .visib_token = pub_tok,
-        .fn_token = fn_tok,
-        .name_token = name_tok,
-        .params_len = fn_params.items.len,
-        .return_type = proto_alias.return_type,
-        .var_args_token = null,
-        .extern_export_inline_token = inline_tok,
-        .body_node = null,
-        .lib_name = null,
-        .align_expr = null,
-        .section_expr = null,
-        .callconv_expr = null,
-    };
-    mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items);
-
     const block_lbrace = try appendToken(c, .LBrace, "{");
 
     const return_expr = try transCreateNodeReturnExpr(c);
-    const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.init_node.?);
+    const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.getTrailer("init_node").?);
 
     const call_expr = try c.createCall(unwrap_expr, fn_params.items.len);
     const call_params = call_expr.params();
@@ -4277,7 +4389,18 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a
         .rbrace = try appendToken(c, .RBrace, "}"),
     };
     block.statements()[0] = &return_expr.base;
-    fn_proto.body_node = &block.base;
+
+    const fn_proto = try ast.Node.FnProto.create(c.arena, .{
+        .params_len = fn_params.items.len,
+        .fn_token = fn_tok,
+        .return_type = proto_alias.return_type,
+    }, .{
+        .visib_token = pub_tok,
+        .name_token = name_tok,
+        .extern_export_inline_token = inline_tok,
+        .body_node = &block.base,
+    });
+    mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items);
     return &fn_proto.base;
 }
 
@@ -4356,31 +4479,6 @@ fn transCreateNodeBreak(c: *Context, label: ?[]const u8) !*ast.Node.ControlFlowE
     return node;
 }
 
-fn transCreateNodeVarDecl(c: *Context, is_pub: bool, is_const: bool, name: []const u8) !*ast.Node.VarDecl {
-    const visib_tok = if (is_pub) try appendToken(c, .Keyword_pub, "pub") else null;
-    const mut_tok = if (is_const) try appendToken(c, .Keyword_const, "const") else try appendToken(c, .Keyword_var, "var");
-    const name_tok = try appendIdentifier(c, name);
-
-    const node = try c.arena.create(ast.Node.VarDecl);
-    node.* = .{
-        .doc_comments = null,
-        .visib_token = visib_tok,
-        .thread_local_token = null,
-        .name_token = name_tok,
-        .eq_token = undefined,
-        .mut_token = mut_tok,
-        .comptime_token = null,
-        .extern_export_token = null,
-        .lib_name = null,
-        .type_node = null,
-        .align_node = null,
-        .section_node = null,
-        .init_node = null,
-        .semicolon_token = undefined,
-    };
-    return node;
-}
-
 fn transCreateNodeWhile(c: *Context) !*ast.Node.While {
     const while_tok = try appendToken(c, .Keyword_while, "while");
     _ = try appendToken(c, .LParen, "(");
@@ -4782,19 +4880,12 @@ fn finishTransFnProto(
         }
     }
 
-    if (is_var_args) {
+    const var_args_token: ?ast.TokenIndex = if (is_var_args) blk: {
         if (param_count > 0) {
             _ = try appendToken(rp.c, .Comma, ",");
         }
-
-        fn_params.addOneAssumeCapacity().* = .{
-            .doc_comments = null,
-            .comptime_token = null,
-            .noalias_token = null,
-            .name_token = null,
-            .param_type = .{ .var_args = try appendToken(rp.c, .Ellipsis3, "...") },
-        };
-    }
+        break :blk try appendToken(rp.c, .Ellipsis3, "...");
+    } else null;
 
     const rparen_tok = try appendToken(rp.c, .RParen, ")");
 
@@ -4860,22 +4951,31 @@ fn finishTransFnProto(
         }
     };
 
-    const fn_proto = try ast.Node.FnProto.alloc(rp.c.arena, fn_params.items.len);
-    fn_proto.* = .{
-        .doc_comments = null,
-        .visib_token = pub_tok,
-        .fn_token = fn_tok,
-        .name_token = name_tok,
+    // We need to reserve an undefined (but non-null) body node to set later.
+    var body_node: ?*ast.Node = null;
+    if (fn_decl_context) |ctx| {
+        if (ctx.has_body) {
+            // TODO: we should be able to use undefined here but
+            // it causes a bug. This is undefined without zig language
+            // being aware of it.
+            body_node = @intToPtr(*ast.Node, 0x08);
+        }
+    }
+
+    const fn_proto = try ast.Node.FnProto.create(rp.c.arena, .{
         .params_len = fn_params.items.len,
         .return_type = .{ .Explicit = return_type_node },
-        .var_args_token = null, // TODO this field is broken in the AST data model
+        .fn_token = fn_tok,
+    }, .{
+        .visib_token = pub_tok,
+        .name_token = name_tok,
         .extern_export_inline_token = extern_export_inline_tok,
-        .body_node = null,
-        .lib_name = null,
         .align_expr = align_expr,
         .section_expr = linksection_expr,
         .callconv_expr = callconv_expr,
-    };
+        .body_node = body_node,
+        .var_args_token = var_args_token,
+    });
     mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items);
     return fn_proto;
 }
@@ -4923,23 +5023,15 @@ pub fn failDecl(c: *Context, loc: ZigClangSourceLocation, name: []const u8, comp
     };
     call_node.params()[0] = &msg_node.base;
 
-    const var_decl_node = try c.arena.create(ast.Node.VarDecl);
-    var_decl_node.* = .{
-        .doc_comments = null,
-        .visib_token = pub_tok,
-        .thread_local_token = null,
+    const var_decl_node = try ast.Node.VarDecl.create(c.arena, .{
         .name_token = name_tok,
-        .eq_token = eq_tok,
         .mut_token = const_tok,
-        .comptime_token = null,
-        .extern_export_token = null,
-        .lib_name = null,
-        .type_node = null,
-        .align_node = null,
-        .section_node = null,
-        .init_node = &call_node.base,
         .semicolon_token = semi_tok,
-    };
+    }, .{
+        .visib_token = pub_tok,
+        .eq_token = eq_tok,
+        .init_node = &call_node.base,
+    });
     try addTopLevelDecl(c, name, &var_decl_node.base);
 }
 
@@ -5132,10 +5224,12 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
 fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
     const scope = &c.global_scope.base;
 
-    const node = try transCreateNodeVarDecl(c, true, true, name);
-    node.eq_token = try appendToken(c, .Equal, "=");
+    const visib_tok = try appendToken(c, .Keyword_pub, "pub");
+    const mut_tok = try appendToken(c, .Keyword_const, "const");
+    const name_tok = try appendIdentifier(c, name);
+    const eq_token = try appendToken(c, .Equal, "=");
 
-    node.init_node = try parseCExpr(c, it, source, source_loc, scope);
+    const init_node = try parseCExpr(c, it, source, source_loc, scope);
     const last = it.next().?;
     if (last.id != .Eof and last.id != .Nl)
         return failDecl(
@@ -5146,7 +5240,16 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n
             .{@tagName(last.id)},
         );
 
-    node.semicolon_token = try appendToken(c, .Semicolon, ";");
+    const semicolon_token = try appendToken(c, .Semicolon, ";");
+    const node = try ast.Node.VarDecl.create(c.arena, .{
+        .name_token = name_tok,
+        .mut_token = mut_tok,
+        .semicolon_token = semicolon_token,
+    }, .{
+        .visib_token = visib_tok,
+        .eq_token = eq_token,
+        .init_node = init_node,
+    });
     _ = try c.global_scope.macro_table.put(name, &node.base);
 }
 
@@ -5223,24 +5326,6 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
 
     const type_of = try c.createBuiltinCall("@TypeOf", 1);
 
-    const fn_proto = try ast.Node.FnProto.alloc(c.arena, fn_params.items.len);
-    fn_proto.* = .{
-        .visib_token = pub_tok,
-        .extern_export_inline_token = inline_tok,
-        .fn_token = fn_tok,
-        .name_token = name_tok,
-        .params_len = fn_params.items.len,
-        .return_type = .{ .Explicit = &type_of.base },
-        .doc_comments = null,
-        .var_args_token = null,
-        .body_node = null,
-        .lib_name = null,
-        .align_expr = null,
-        .section_expr = null,
-        .callconv_expr = null,
-    };
-    mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items);
-
     const return_expr = try transCreateNodeReturnExpr(c);
     const expr = try parseCExpr(c, it, source, source_loc, scope);
     const last = it.next().?;
@@ -5266,7 +5351,18 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8,
 
     try block_scope.statements.append(&return_expr.base);
     const block_node = try block_scope.complete(c);
-    fn_proto.body_node = &block_node.base;
+    const fn_proto = try ast.Node.FnProto.create(c.arena, .{
+        .fn_token = fn_tok,
+        .params_len = fn_params.items.len,
+        .return_type = .{ .Explicit = &type_of.base },
+    }, .{
+        .visib_token = pub_tok,
+        .extern_export_inline_token = inline_tok,
+        .name_token = name_tok,
+        .body_node = &block_node.base,
+    });
+    mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items);
+
     _ = try c.global_scope.macro_table.put(name, &fn_proto.base);
 }
 
@@ -6030,7 +6126,7 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node {
             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.?);
+                    return getContainer(c, var_decl.getTrailer("init_node").?);
             }
         },
 
@@ -6060,7 +6156,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node {
     if (ref.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| {
-                if (var_decl.type_node) |ty|
+                if (var_decl.getTrailer("type_node")) |ty|
                     return getContainer(c, ty);
             }
         }
@@ -6084,7 +6180,7 @@ 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;
+    const init = if (ref.cast(ast.Node.VarDecl)) |v| v.getTrailer("init_node").? else return null;
     if (getContainerTypeOf(c, init)) |ty_node| {
         if (ty_node.cast(ast.Node.OptionalType)) |prefix| {
             if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| {
test/translate_c.zig
@@ -2797,7 +2797,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\pub fn a() callconv(.C) void {}
         \\pub fn b() callconv(.C) void {}
         \\pub export fn c() void {}
-        \\pub fn foo() callconv(.C) void {}
+        \\pub fn foo(...) callconv(.C) void {}
     });
 
     cases.add("casting away const and volatile",