Commit 338a495648

Vexu <git@vexu.eu>
2020-08-19 12:56:48
stage2: implement global variables
1 parent b0846b6
src-self-hosted/codegen.zig
@@ -684,6 +684,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .unreach => return MCValue{ .unreach = {} },
                 .unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?),
                 .wrap_optional => return self.genWrapOptional(inst.castTag(.wrap_optional).?),
+                .varptr => return self.genVarPtr(inst.castTag(.varptr).?),
             }
         }
 
@@ -858,6 +859,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        fn genVarPtr(self: *Self, inst: *ir.Inst.VarPtr) !MCValue {
+            // No side effects, so if it's unreferenced, do nothing.
+            if (inst.base.isUnused())
+                return MCValue.dead;
+
+            switch (arch) {
+                else => return self.fail(inst.base.src, "TODO implement varptr for {}", .{self.target.cpu.arch}),
+            }
+        }
+
         fn reuseOperand(inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool {
             if (!inst.operandDies(op_index) or !mcv.isMutable())
                 return false;
src-self-hosted/ir.zig
@@ -81,6 +81,7 @@ pub const Inst = struct {
         ref,
         ret,
         retvoid,
+        varptr,
         /// Write a value to a pointer. LHS is pointer, RHS is value.
         store,
         sub,
@@ -135,6 +136,7 @@ pub const Inst = struct {
                 .condbr => CondBr,
                 .constant => Constant,
                 .loop => Loop,
+                .varptr => VarPtr,
             };
         }
 
@@ -434,6 +436,20 @@ pub const Inst = struct {
             return null;
         }
     };
+
+    pub const VarPtr = struct {
+        pub const base_tag = Tag.varptr;
+
+        base: Inst,
+        variable: *Module.Var,
+
+        pub fn operandCount(self: *const VarPtr) usize {
+            return 0;
+        }
+        pub fn getOperand(self: *const VarPtr, index: usize) ?*Inst {
+            return null;
+        }
+    };
 };
 
 pub const Body = struct {
src-self-hosted/Module.zig
@@ -320,6 +320,12 @@ pub const Fn = struct {
     }
 };
 
+pub const Var = struct {
+    value: ?Value,
+    owner_decl: *Decl,
+    is_mutable: bool,
+};
+
 pub const Scope = struct {
     tag: Tag,
 
@@ -1419,7 +1425,152 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
             }
             return type_changed;
         },
-        .VarDecl => @panic("TODO var decl"),
+        .VarDecl => {
+            const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", ast_node);
+
+            decl.analysis = .in_progress;
+
+            const is_extern = blk: {
+                const maybe_extern_token = var_decl.getTrailer("extern_export_token") orelse
+                    break :blk false;
+                break :blk tree.token_ids[maybe_extern_token] == .Keyword_extern;
+            };
+            const is_mutable = tree.token_ids[var_decl.mut_token] == .Keyword_var;
+
+            // We need the memory for the Type to go into the arena for the Decl
+            var decl_arena = std.heap.ArenaAllocator.init(self.gpa);
+            errdefer decl_arena.deinit();
+            const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
+
+            var block_scope: Scope.Block = .{
+                .parent = null,
+                .func = null,
+                .decl = decl,
+                .instructions = .{},
+                .arena = &decl_arena.allocator,
+            };
+            defer block_scope.instructions.deinit(self.gpa);
+
+            const explicit_type = blk: {
+                const type_node = var_decl.getTrailer("type_node") orelse
+                    break :blk null;
+
+                var type_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
+                defer type_scope_arena.deinit();
+                var type_scope: Scope.GenZIR = .{
+                    .decl = decl,
+                    .arena = &type_scope_arena.allocator,
+                    .parent = decl.scope,
+                };
+                defer type_scope.instructions.deinit(self.gpa);
+
+                const src = tree.token_locs[type_node.firstToken()].start;
+                const type_type = try astgen.addZIRInstConst(self, &type_scope.base, src, .{
+                    .ty = Type.initTag(.type),
+                    .val = Value.initTag(.type_type),
+                });
+                const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node);
+                _ = try astgen.addZIRUnOp(self, &type_scope.base, src, .@"return", var_type);
+
+                break :blk try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{
+                    .instructions = type_scope.instructions.items,
+                });
+            };
+
+            var var_type: Type = undefined;
+            const value: ?Value = if (var_decl.getTrailer("init_node")) |init_node| blk: {
+                var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa);
+                defer gen_scope_arena.deinit();
+                var gen_scope: Scope.GenZIR = .{
+                    .decl = decl,
+                    .arena = &gen_scope_arena.allocator,
+                    .parent = decl.scope,
+                };
+                defer gen_scope.instructions.deinit(self.gpa);
+                const src = tree.token_locs[init_node.firstToken()].start;
+
+                // TODO comptime scope here
+                const init_inst = try astgen.expr(self, &gen_scope.base, .none, init_node);
+                _ = try astgen.addZIRUnOp(self, &gen_scope.base, src, .@"return", init_inst);
+
+                var inner_block: Scope.Block = .{
+                    .parent = null,
+                    .func = null,
+                    .decl = decl,
+                    .instructions = .{},
+                    .arena = &gen_scope_arena.allocator,
+                };
+                defer inner_block.instructions.deinit(self.gpa);
+                try zir_sema.analyzeBody(self, &inner_block.base, .{ .instructions = gen_scope.instructions.items });
+
+                for (inner_block.instructions.items) |inst| {
+                    if (inst.castTag(.ret)) |ret| {
+                        const coerced = if (explicit_type) |some|
+                            try self.coerce(&inner_block.base, some, ret.operand)
+                        else
+                            ret.operand;
+                        const val = try self.resolveConstValue(&inner_block.base, coerced);
+
+                        var_type = explicit_type orelse try ret.operand.ty.copy(block_scope.arena);
+                        break :blk try val.copy(block_scope.arena);
+                    } else {
+                        return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{});
+                    }
+                }
+                unreachable;
+            } else if (!is_extern) {
+                return self.failTok(&block_scope.base, var_decl.firstToken(), "variables must be initialized", .{});
+            } else if (explicit_type) |some| blk: {
+                var_type = some;
+                break :blk null;
+            } else {
+                return self.failTok(&block_scope.base, var_decl.firstToken(), "unable to infer variable type", .{});
+            };
+
+            if (is_mutable and !var_type.isValidVarType(is_extern)) {
+                return self.failTok(&block_scope.base, var_decl.firstToken(), "variable of type '{}' must be const", .{var_type});
+            }
+
+            var type_changed = true;
+            if (decl.typedValueManaged()) |tvm| {
+                type_changed = !tvm.typed_value.ty.eql(var_type);
+
+                tvm.deinit(self.gpa);
+            }
+
+            const new_variable = try decl_arena.allocator.create(Var);
+            const var_payload = try decl_arena.allocator.create(Value.Payload.Variable);
+            new_variable.* = .{
+                .value = value,
+                .owner_decl = decl,
+                .is_mutable = is_mutable,
+            };
+            var_payload.* = .{ .variable = new_variable };
+
+            decl_arena_state.* = decl_arena.state;
+            decl.typed_value = .{
+                .most_recent = .{
+                    .typed_value = .{
+                        .ty = var_type,
+                        .val = Value.initPayload(&var_payload.base),
+                    },
+                    .arena = decl_arena_state,
+                },
+            };
+            decl.analysis = .complete;
+            decl.generation = self.generation;
+
+            if (var_decl.getTrailer("extern_export_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[var_decl.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);
+                }
+            }
+            return type_changed;
+        },
         .Comptime => @panic("TODO comptime decl"),
         .Use => @panic("TODO usingnamespace decl"),
         else => unreachable,
@@ -1584,7 +1735,32 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
                 }
             }
         } else if (src_decl.castTag(.VarDecl)) |var_decl| {
-            log.err("TODO: analyze var decl", .{});
+            const name_loc = tree.token_locs[var_decl.name_token];
+            const name = tree.tokenSliceLoc(name_loc);
+            const name_hash = root_scope.fullyQualifiedNameHash(name);
+            const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl));
+            if (self.decl_table.get(name_hash)) |decl| {
+                // Update the AST Node index of the decl, even if its contents are unchanged, it may
+                // have been re-ordered.
+                decl.src_index = decl_i;
+                if (deleted_decls.remove(decl) == null) {
+                    decl.analysis = .sema_failure;
+                    const err_msg = try ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{}'", .{decl.name});
+                    errdefer err_msg.destroy(self.gpa);
+                    try self.failed_decls.putNoClobber(self.gpa, decl, err_msg);
+                } else if (!srcHashEql(decl.contents_hash, contents_hash)) {
+                    try self.markOutdatedDecl(decl);
+                    decl.contents_hash = contents_hash;
+                }
+            } else {
+                const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash);
+                root_scope.decls.appendAssumeCapacity(new_decl);
+                if (var_decl.getTrailer("extern_export_token")) |maybe_export_token| {
+                    if (tree.token_ids[maybe_export_token] == .Keyword_export) {
+                        self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl });
+                    }
+                }
+            }
         } else if (src_decl.castTag(.Comptime)) |comptime_node| {
             log.err("TODO: analyze comptime decl", .{});
         } else if (src_decl.castTag(.ContainerField)) |container_field| {
@@ -2217,20 +2393,46 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn
     };
 
     const decl_tv = try decl.typedValue();
-    const ty_payload = try scope.arena().create(Type.Payload.Pointer);
-    ty_payload.* = .{
-        .base = .{ .tag = .single_const_pointer },
-        .pointee_type = decl_tv.ty,
-    };
+    if (decl_tv.val.tag() == .variable) {
+        return self.getVarRef(scope, src, decl_tv);
+    }
+    const ty = try self.singlePtrType(scope, src, false, decl_tv.ty);
     const val_payload = try scope.arena().create(Value.Payload.DeclRef);
     val_payload.* = .{ .decl = decl };
 
     return self.constInst(scope, src, .{
-        .ty = Type.initPayload(&ty_payload.base),
+        .ty = ty,
         .val = Value.initPayload(&val_payload.base),
     });
 }
 
+fn getVarRef(self: *Module, scope: *Scope, src: usize, tv: TypedValue) InnerError!*Inst {
+    const variable = tv.val.cast(Value.Payload.Variable).?.variable;
+
+    const ty = try self.singlePtrType(scope, src, variable.is_mutable, tv.ty);
+    if (!variable.is_mutable and variable.value != null) {
+        const val_payload = try scope.arena().create(Value.Payload.RefVal);
+        val_payload.* = .{ .val = variable.value.? };
+        return self.constInst(scope, src, .{
+            .ty = ty,
+            .val = Value.initPayload(&val_payload.base),
+        });
+    }
+    
+    const b = try self.requireRuntimeBlock(scope, src);
+    const inst = try b.arena.create(Inst.VarPtr);
+    inst.* = .{
+        .base = .{
+            .tag = .varptr,
+            .ty = ty,
+            .src = src,
+        },
+        .variable = variable,
+    };
+    try b.instructions.append(self.gpa, &inst.base);
+    return &inst.base;
+}
+
 pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst {
     const elem_ty = switch (ptr.ty.zigTypeTag()) {
         .Pointer => ptr.ty.elemType(),
src-self-hosted/type.zig
@@ -1074,7 +1074,7 @@ pub const Type = extern union {
     }
 
     /// Returns if type can be used for a runtime variable
-    pub fn isValidVarType(self: Type) bool {
+    pub fn isValidVarType(self: Type, is_extern: bool) bool {
         var ty = self;
         while (true) switch (ty.zigTypeTag()) {
             .Bool,
@@ -1087,6 +1087,7 @@ pub const Type = extern union {
             .Vector,
             => return true,
 
+            .Opaque => return is_extern,
             .BoundFn,
             .ComptimeFloat,
             .ComptimeInt,
@@ -1096,12 +1097,11 @@ pub const Type = extern union {
             .Void,
             .Undefined,
             .Null,
-            .Opaque,
             => return false,
 
             .Optional => {
                 var buf: Payload.Pointer = undefined;
-                return ty.optionalChild(&buf).isValidVarType();
+                return ty.optionalChild(&buf).isValidVarType(is_extern);
             },
             .Pointer, .Array => ty = ty.elemType(),
 
src-self-hosted/value.zig
@@ -79,6 +79,7 @@ pub const Value = extern union {
         int_big_positive,
         int_big_negative,
         function,
+        variable,
         ref_val,
         decl_ref,
         elem_ptr,
@@ -196,6 +197,7 @@ pub const Value = extern union {
                 @panic("TODO implement copying of big ints");
             },
             .function => return self.copyPayloadShallow(allocator, Payload.Function),
+            .variable => return self.copyPayloadShallow(allocator, Payload.Variable),
             .ref_val => {
                 const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise);
                 const new_payload = try allocator.create(Payload.RefVal);
@@ -310,6 +312,7 @@ pub const Value = extern union {
             .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
             .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
             .function => return out_stream.writeAll("(function)"),
+            .variable => return out_stream.writeAll("(variable)"),
             .ref_val => {
                 const ref_val = val.cast(Payload.RefVal).?;
                 try out_stream.writeAll("&const ");
@@ -410,6 +413,7 @@ pub const Value = extern union {
             .int_big_positive,
             .int_big_negative,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -471,6 +475,7 @@ pub const Value = extern union {
             .enum_literal_type,
             .null_value,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -548,6 +553,7 @@ pub const Value = extern union {
             .enum_literal_type,
             .null_value,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -625,6 +631,7 @@ pub const Value = extern union {
             .enum_literal_type,
             .null_value,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -728,6 +735,7 @@ pub const Value = extern union {
             .enum_literal_type,
             .null_value,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -810,6 +818,7 @@ pub const Value = extern union {
             .enum_literal_type,
             .null_value,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -974,6 +983,7 @@ pub const Value = extern union {
             .bool_false,
             .null_value,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -1046,6 +1056,7 @@ pub const Value = extern union {
             .enum_literal_type,
             .null_value,
             .function,
+            .variable,
             .ref_val,
             .decl_ref,
             .elem_ptr,
@@ -1182,6 +1193,7 @@ pub const Value = extern union {
             .bool_false,
             .null_value,
             .function,
+            .variable,
             .int_u64,
             .int_i64,
             .int_big_positive,
@@ -1260,6 +1272,7 @@ pub const Value = extern union {
             .bool_false,
             .null_value,
             .function,
+            .variable,
             .int_u64,
             .int_i64,
             .int_big_positive,
@@ -1355,6 +1368,7 @@ pub const Value = extern union {
             .bool_true,
             .bool_false,
             .function,
+            .variable,
             .int_u64,
             .int_i64,
             .int_big_positive,
@@ -1429,6 +1443,11 @@ pub const Value = extern union {
             func: *Module.Fn,
         };
 
+        pub const Variable = struct {
+            base: Payload = Payload{ .tag = .variable },
+            variable: *Module.Var,
+        };
+
         pub const ArraySentinel0_u8_Type = struct {
             base: Payload = Payload{ .tag = .array_sentinel_0_u8_type },
             len: u64,
src-self-hosted/zir.zig
@@ -1752,6 +1752,9 @@ const EmitZIR = struct {
                 const decl_ref = try self.emitDeclRef(inst.src, declref.decl);
                 try new_body.instructions.append(decl_ref);
                 break :blk decl_ref;
+            } else if (const_inst.val.cast(Value.Payload.Variable)) |var_pl| blk: {
+                const owner_decl = var_pl.variable.owner_decl;
+                break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name));
             } else blk: {
                 break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst;
             };
@@ -2311,6 +2314,8 @@ const EmitZIR = struct {
                     };
                     break :blk &new_inst.base;
                 },
+
+                .varptr => @panic("TODO"),
             };
             try self.metadata.put(new_inst, .{ .deaths = inst.deaths });
             try instructions.append(new_inst);
src-self-hosted/zir_sema.zig
@@ -366,7 +366,7 @@ fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.
 fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const var_type = try resolveType(mod, scope, inst.positionals.operand);
     // TODO this should happen only for var allocs
-    if (!var_type.isValidVarType()) {
+    if (!var_type.isValidVarType(false)) {
         return mod.fail(scope, inst.base.src, "variable of type '{}' must be const or comptime", .{var_type});
     }
     const ptr_type = try mod.singlePtrType(scope, inst.base.src, true, var_type);
@@ -779,7 +779,7 @@ fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) Inne
     for (fntype.positionals.param_types) |param_type, i| {
         const resolved = try resolveType(mod, scope, param_type);
         // TODO skip for comptime params
-        if (!resolved.isValidVarType()) {
+        if (!resolved.isValidVarType(false)) {
             return mod.fail(scope, param_type.src, "parameter of type '{}' must be declared comptime", .{resolved});
         }
         param_types[i] = resolved;