From 338a495648f07a2734e012034fd301a0e77f8202 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 19 Aug 2020 13:56:48 +0300 Subject: [PATCH 1/5] stage2: implement global variables --- src-self-hosted/Module.zig | 218 +++++++++++++++++++++++++++++++++-- src-self-hosted/codegen.zig | 11 ++ src-self-hosted/ir.zig | 16 +++ src-self-hosted/type.zig | 6 +- src-self-hosted/value.zig | 19 +++ src-self-hosted/zir.zig | 5 + src-self-hosted/zir_sema.zig | 4 +- 7 files changed, 266 insertions(+), 13 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index e5988dfd8a..3a68063236 100644 --- a/src-self-hosted/Module.zig +++ b/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(), diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index cb404d8315..1bc76f6b05 100644 --- a/src-self-hosted/codegen.zig +++ b/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; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 070f41eb5e..ff90c68d42 100644 --- a/src-self-hosted/ir.zig +++ b/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 { diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index a6ec90a35b..82671609d0 100644 --- a/src-self-hosted/type.zig +++ b/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(), diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index b6356d9b17..7bebd022ab 100644 --- a/src-self-hosted/value.zig +++ b/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, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 1a2c5cebaa..f59d470ffd 100644 --- a/src-self-hosted/zir.zig +++ b/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); diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index fb39a1e077..644b73d643 100644 --- a/src-self-hosted/zir_sema.zig +++ b/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; From ab8a9a6605900fc0b064c321504ac4e7dc1ca6f4 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 19 Aug 2020 14:25:47 +0300 Subject: [PATCH 2/5] stage2: fix astgen of decl ref, add test for global consts --- src-self-hosted/astgen.zig | 14 ++++++++------ src-self-hosted/type.zig | 3 ++- src-self-hosted/zir_sema.zig | 3 +-- test/stage2/compare_output.zig | 31 +++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 9d40c7899a..9f8afa6225 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -1223,9 +1223,10 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo } if (mod.lookupDeclName(scope, ident_name)) |decl| { - // TODO handle lvalues const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); - return rlWrap(mod, scope, rl, result); + if (rl == .lvalue or rl == .ref) + return result; + return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, result)); } return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); @@ -1258,7 +1259,8 @@ fn multilineStrLiteral(mod: *Module, scope: *Scope, node: *ast.Node.MultilineStr // line lengths and new lines var len = lines.len - 1; for (lines) |line| { - len += tree.tokenSlice(line).len - 2; + // 2 for the '//' + 1 for '\n' + len += tree.tokenSlice(line).len - 3; } const bytes = try scope.arena().alloc(u8, len); @@ -1268,9 +1270,9 @@ fn multilineStrLiteral(mod: *Module, scope: *Scope, node: *ast.Node.MultilineStr bytes[i] = '\n'; i += 1; } - const slice = tree.tokenSlice(line)[2..]; - mem.copy(u8, bytes[i..], slice); - i += slice.len; + const slice = tree.tokenSlice(line); + mem.copy(u8, bytes[i..], slice[2..slice.len - 1]); + i += slice.len - 3; } return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 82671609d0..c2762e1f8a 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -457,7 +457,8 @@ pub const Type = extern union { try param_type.format("", .{}, out_stream); } try out_stream.writeAll(") "); - try payload.return_type.format("", .{}, out_stream); + ty = payload.return_type; + continue; }, .array_u8 => { diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 644b73d643..cb1ed08d6d 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -581,8 +581,7 @@ fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) Inne fn analyzeInstDeclValInModule(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { const decl = inst.positionals.decl; - const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl); - return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); + return mod.analyzeDeclRef(scope, inst.base.src, decl); } fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 3513605602..2b8c8e94cc 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -617,6 +617,37 @@ pub fn addCases(ctx: *TestContext) !void { , "", ); + + case.addCompareOutput( + \\export fn _start() noreturn { + \\ add(aa, bb); + \\ + \\ exit(); + \\} + \\ + \\const aa = 'ぁ'; + \\const bb = '\x03'; + \\ + \\fn add(a: u32, b: u32) void { + \\ assert(a + b == 12356); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); } { From 9801047bdb26c86da430310aa5d043f6899adbfe Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 19 Aug 2020 15:19:00 +0300 Subject: [PATCH 3/5] stage2: handle var attributes --- src-self-hosted/Module.zig | 50 ++++++++++++++++++++++++++++-------- src-self-hosted/zir_sema.zig | 1 + 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 3a68063236..8009bcc32d 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -290,6 +290,8 @@ pub const Fn = struct { }, owner_decl: *Decl, + is_pub: bool, + /// This memory is temporary and points to stack memory for the duration /// of Fn analysis. pub const Analysis = struct { @@ -323,7 +325,11 @@ pub const Fn = struct { pub const Var = struct { value: ?Value, owner_decl: *Decl, + + is_pub: bool, + is_extern: bool, is_mutable: bool, + is_threadlocal: bool, }; pub const Scope = struct { @@ -1241,6 +1247,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer fn_type_scope.instructions.deinit(self.gpa); + const is_pub = fn_proto.getTrailer("visib_token") != null; const body_node = fn_proto.getTrailer("body_node") orelse return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); @@ -1378,6 +1385,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { new_func.* = .{ .analysis = .{ .queued = fn_zir }, .owner_decl = decl, + .is_pub = is_pub, }; fn_payload.* = .{ .func = new_func }; @@ -1430,13 +1438,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { 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(); @@ -1451,6 +1452,32 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer block_scope.instructions.deinit(self.gpa); + const is_pub = var_decl.getTrailer("visib_token") != null; + 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; + }; + if (var_decl.getTrailer("lib_name")) |lib_name| { + assert(is_extern); + return self.failNode(&block_scope.base, lib_name, "TODO implement function library name", .{}); + } + const is_mutable = tree.token_ids[var_decl.mut_token] == .Keyword_var; + const is_threadlocal = if (var_decl.getTrailer("thread_local_token")) |some| blk: { + if (!is_mutable) { + return self.failTok(&block_scope.base, some, "threadlocal variable cannot be constant", .{}); + } + break :blk true; + } else false; + assert(var_decl.getTrailer("comptime_token") == null); + if (var_decl.getTrailer("align_node")) |align_expr| { + return self.failNode(&block_scope.base, align_expr, "TODO implement function align expression", .{}); + } + if (var_decl.getTrailer("section_node")) |sect_expr| { + return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{}); + } + + const explicit_type = blk: { const type_node = var_decl.getTrailer("type_node") orelse break :blk null; @@ -1543,7 +1570,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { new_variable.* = .{ .value = value, .owner_decl = decl, + .is_pub = is_pub, + .is_extern = is_extern, .is_mutable = is_mutable, + .is_threadlocal = is_threadlocal, }; var_payload.* = .{ .variable = new_variable }; @@ -2394,7 +2424,7 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn const decl_tv = try decl.typedValue(); if (decl_tv.val.tag() == .variable) { - return self.getVarRef(scope, src, decl_tv); + return self.analyzeVarRef(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); @@ -2406,11 +2436,11 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn }); } -fn getVarRef(self: *Module, scope: *Scope, src: usize, tv: TypedValue) InnerError!*Inst { +fn analyzeVarRef(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) { + if (!variable.is_mutable and !variable.is_extern and variable.value != null) { const val_payload = try scope.arena().create(Value.Payload.RefVal); val_payload.* = .{ .val = variable.value.? }; return self.constInst(scope, src, .{ diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index cb1ed08d6d..87cce3adb3 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -666,6 +666,7 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError! new_func.* = .{ .analysis = .{ .queued = fn_zir }, .owner_decl = scope.decl().?, + .is_pub = false, }; const fn_payload = try scope.arena().create(Value.Payload.Function); fn_payload.* = .{ .func = new_func }; From 5fdcb1a792e271c67f3aec36f8aca9e6c5503013 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 19 Aug 2020 19:12:01 +0300 Subject: [PATCH 4/5] stage2: emit zir variable fix, array type and enum literal support --- src-self-hosted/value.zig | 11 ++++++- src-self-hosted/zir.zig | 65 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 7bebd022ab..95a0746e19 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -218,7 +218,7 @@ pub const Value = extern union { }; return Value{ .ptr_otherwise = &new_payload.base }; }, - .enum_literal, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), + .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), .repeated => { const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise); const new_payload = try allocator.create(Payload.Repeated); @@ -232,6 +232,15 @@ pub const Value = extern union { .float_32 => return self.copyPayloadShallow(allocator, Payload.Float_32), .float_64 => return self.copyPayloadShallow(allocator, Payload.Float_64), .float_128 => return self.copyPayloadShallow(allocator, Payload.Float_128), + .enum_literal => { + const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.Bytes); + new_payload.* = .{ + .base = payload.base, + .data = try allocator.dupe(u8, payload.data), + }; + return Value{ .ptr_otherwise = &new_payload.base }; + }, } } diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index f59d470ffd..384463caee 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1878,6 +1878,11 @@ const EmitZIR = struct { if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| { const decl = decl_ref.decl; return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); + } else if (typed_value.val.cast(Value.Payload.Variable)) |variable| { + return self.emitTypedValue(src, .{ + .ty = typed_value.ty, + .val = variable.variable.value.?, + }); } if (typed_value.val.isUndef()) { const as_inst = try self.arena.allocator.create(Inst.BinOp); @@ -1967,6 +1972,21 @@ const EmitZIR = struct { return self.emitPrimitive(src, .@"true") else return self.emitPrimitive(src, .@"false"), + .EnumLiteral => { + const enum_literal = @fieldParentPtr(Value.Payload.Bytes, "base", typed_value.val.ptr_otherwise); + const inst = try self.arena.allocator.create(Inst.Str); + inst.* = .{ + .base = .{ + .src = src, + .tag = .enum_literal, + }, + .positionals = .{ + .bytes = enum_literal.data, + }, + .kw_args = .{}, + }; + return self.emitUnnamedDecl(&inst.base); + }, else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}), } } @@ -2437,6 +2457,51 @@ const EmitZIR = struct { }; return self.emitUnnamedDecl(&inst.base); }, + .Array => { + var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() }; + const len = Value.initPayload(&len_pl.base); + + const inst = if (ty.arraySentinel()) |sentinel| blk: { + const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel); + inst.* = .{ + .base = .{ + .src = src, + .tag = .array_type, + }, + .positionals = .{ + .len = (try self.emitTypedValue(src, .{ + .ty = Type.initTag(.usize), + .val = len, + })).inst, + .sentinel = (try self.emitTypedValue(src, .{ + .ty = ty.elemType(), + .val = sentinel, + })).inst, + .elem_type = (try self.emitType(src, ty.elemType())).inst, + }, + .kw_args = .{}, + }; + break :blk &inst.base; + } else blk: { + const inst = try self.arena.allocator.create(Inst.BinOp); + inst.* = .{ + .base = .{ + .src = src, + .tag = .array_type, + }, + .positionals = .{ + .lhs = (try self.emitTypedValue(src, .{ + .ty = Type.initTag(.usize), + .val = len, + })).inst, + .rhs = (try self.emitType(src, ty.elemType())).inst, + }, + .kw_args = .{}, + }; + break :blk &inst.base; + }; + return self.emitUnnamedDecl(inst); + }, else => std.debug.panic("TODO implement emitType for {}", .{ty}), }, } From 9ec9c0f5e57820f4baa04b9674a6a4a88235b863 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Aug 2020 17:52:22 -0700 Subject: [PATCH 5/5] optimize the memory layout of Module.Fn and Module.Var `is_pub` added to `Fn` would cost us an additional 8 bytes of memory per function, which is a real bummer since it's only 1 bit of information. If we wanted to really remove this, I suspect we could make this a function isPub() which looks at the AST of the corresponding Decl and finds if the FnProto AST node has the pub token. However I saw an easier approach - The data of whether something is pub or not is actually a property of a Decl anyway, not a function, so we can look at moving the field into Decl. Indeed, doing this, we see that Decl already has deletion_flag: bool which is hiding in the padding bytes between the enum (1 byte) and the following u32 field (generation). So if we put the is_pub bool there, it actually will take up no additional space, with 1 byte of padding remaining. This was an easy reworking of the code since any func.is_pub could be changed simply to func.owner_decl.is_pub. I also modified `Var` to make the init value non-optional and moved the optional bit to a has_init: bool field. This is worse from the perspective of control flow and safety, however it makes `@sizeOf(Var)` go from 32 bytes to 24 bytes. The more code we can fit into memory at once, the more justified we are in using the compiler as a long-running process that does incremental updates. --- src-self-hosted/Module.zig | 26 +++++++++++++------------- src-self-hosted/zir.zig | 2 +- src-self-hosted/zir_sema.zig | 1 - test/stage2/compare_output.zig | 2 ++ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 8009bcc32d..ec69f94e3d 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -170,6 +170,9 @@ pub const Decl = struct { /// This flag is set when this Decl is added to a check_for_deletion set, and cleared /// when removed. deletion_flag: bool, + /// Whether the corresponding AST decl has a `pub` keyword. + is_pub: bool, + /// An integer that can be checked against the corresponding incrementing /// generation field of Module. This is used to determine whether `complete` status /// represents pre- or post- re-analysis. @@ -290,8 +293,6 @@ pub const Fn = struct { }, owner_decl: *Decl, - is_pub: bool, - /// This memory is temporary and points to stack memory for the duration /// of Fn analysis. pub const Analysis = struct { @@ -323,10 +324,10 @@ pub const Fn = struct { }; pub const Var = struct { - value: ?Value, + init: Value, owner_decl: *Decl, - is_pub: bool, + has_init: bool, is_extern: bool, is_mutable: bool, is_threadlocal: bool, @@ -1247,7 +1248,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer fn_type_scope.instructions.deinit(self.gpa); - const is_pub = fn_proto.getTrailer("visib_token") != null; + decl.is_pub = fn_proto.getTrailer("visib_token") != null; const body_node = fn_proto.getTrailer("body_node") orelse return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); @@ -1385,7 +1386,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { new_func.* = .{ .analysis = .{ .queued = fn_zir }, .owner_decl = decl, - .is_pub = is_pub, }; fn_payload.* = .{ .func = new_func }; @@ -1452,7 +1452,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer block_scope.instructions.deinit(self.gpa); - const is_pub = var_decl.getTrailer("visib_token") != null; + decl.is_pub = var_decl.getTrailer("visib_token") != null; const is_extern = blk: { const maybe_extern_token = var_decl.getTrailer("extern_export_token") orelse break :blk false; @@ -1477,7 +1477,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { return self.failNode(&block_scope.base, sect_expr, "TODO implement function section expression", .{}); } - const explicit_type = blk: { const type_node = var_decl.getTrailer("type_node") orelse break :blk null; @@ -1568,9 +1567,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { 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_pub = is_pub, + .init = value orelse undefined, + .has_init = value != null, .is_extern = is_extern, .is_mutable = is_mutable, .is_threadlocal = is_threadlocal, @@ -2004,6 +2003,7 @@ fn allocateNewDecl( .wasm => .{ .wasm = null }, }, .generation = 0, + .is_pub = false, }; return new_decl; } @@ -2440,15 +2440,15 @@ fn analyzeVarRef(self: *Module, scope: *Scope, src: usize, tv: TypedValue) Inner 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.is_extern and variable.value != null) { + if (!variable.is_mutable and !variable.is_extern and variable.has_init) { const val_payload = try scope.arena().create(Value.Payload.RefVal); - val_payload.* = .{ .val = variable.value.? }; + val_payload.* = .{ .val = variable.init }; 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.* = .{ diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 384463caee..552e90956b 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1881,7 +1881,7 @@ const EmitZIR = struct { } else if (typed_value.val.cast(Value.Payload.Variable)) |variable| { return self.emitTypedValue(src, .{ .ty = typed_value.ty, - .val = variable.variable.value.?, + .val = variable.variable.init, }); } if (typed_value.val.isUndef()) { diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 87cce3adb3..cb1ed08d6d 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -666,7 +666,6 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError! new_func.* = .{ .analysis = .{ .queued = fn_zir }, .owner_decl = scope.decl().?, - .is_pub = false, }; const fn_payload = try scope.arena().create(Value.Payload.Function); fn_payload.* = .{ .func = new_func }; diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 2b8c8e94cc..6fbc760f26 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -586,6 +586,7 @@ pub fn addCases(ctx: *TestContext) !void { "", ); + // Character literals and multiline strings. case.addCompareOutput( \\export fn _start() noreturn { \\ const ignore = @@ -618,6 +619,7 @@ pub fn addCases(ctx: *TestContext) !void { "", ); + // Global const. case.addCompareOutput( \\export fn _start() noreturn { \\ add(aa, bb);