From 687bd92f9c3d9f521c8fe5884627ef1b00320364 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 12 Jul 2018 15:08:40 -0400 Subject: [PATCH] self-hosted: generate zig IR for simple function no tests for this yet. I think the quickest path to testing will be creating the .o files and linking with libc, executing, and then comparing output. --- src-self-hosted/decl.zig | 96 ++++ src-self-hosted/ir.zig | 745 +++++++++++++++++++++++++++----- src-self-hosted/module.zig | 383 ++++++++-------- src-self-hosted/parsed_file.zig | 6 + src-self-hosted/scope.zig | 230 +++++++++- src-self-hosted/type.zig | 268 ++++++++++++ src-self-hosted/value.zig | 125 ++++++ src-self-hosted/visib.zig | 4 + std/event/future.zig | 2 +- std/zig/ast.zig | 6 - std/zig/parse.zig | 5 - 11 files changed, 1555 insertions(+), 315 deletions(-) create mode 100644 src-self-hosted/decl.zig create mode 100644 src-self-hosted/parsed_file.zig create mode 100644 src-self-hosted/type.zig create mode 100644 src-self-hosted/value.zig create mode 100644 src-self-hosted/visib.zig diff --git a/src-self-hosted/decl.zig b/src-self-hosted/decl.zig new file mode 100644 index 0000000000..1a75a3249e --- /dev/null +++ b/src-self-hosted/decl.zig @@ -0,0 +1,96 @@ +const std = @import("std"); +const Allocator = mem.Allocator; +const mem = std.mem; +const ast = std.zig.ast; +const Visib = @import("visib.zig").Visib; +const ParsedFile = @import("parsed_file.zig").ParsedFile; +const event = std.event; +const Value = @import("value.zig").Value; +const Token = std.zig.Token; +const errmsg = @import("errmsg.zig"); +const Scope = @import("scope.zig").Scope; +const Module = @import("module.zig").Module; + +pub const Decl = struct { + id: Id, + name: []const u8, + visib: Visib, + resolution: event.Future(Module.BuildError!void), + resolution_in_progress: u8, + parsed_file: *ParsedFile, + parent_scope: *Scope, + + pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8); + + pub fn isExported(base: *const Decl, tree: *ast.Tree) bool { + switch (base.id) { + Id.Fn => { + const fn_decl = @fieldParentPtr(Fn, "base", base); + return fn_decl.isExported(tree); + }, + else => return false, + } + } + + pub fn getSpan(base: *const Decl) errmsg.Span { + switch (base.id) { + Id.Fn => { + const fn_decl = @fieldParentPtr(Fn, "base", base); + const fn_proto = fn_decl.fn_proto; + const start = fn_proto.fn_token; + const end = fn_proto.name_token orelse start; + return errmsg.Span{ + .first = start, + .last = end + 1, + }; + }, + else => @panic("TODO"), + } + } + + pub const Id = enum { + Var, + Fn, + CompTime, + }; + + pub const Var = struct { + base: Decl, + }; + + pub const Fn = struct { + base: Decl, + value: Val, + fn_proto: *const ast.Node.FnProto, + + // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous + pub const Val = union { + Unresolved: void, + Ok: *Value.Fn, + }; + + pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { + return if (self.fn_proto.extern_export_inline_token) |tok_index| x: { + const token = tree.tokens.at(tok_index); + break :x switch (token.id) { + Token.Id.Extern => tree.tokenSlicePtr(token), + else => null, + }; + } else null; + } + + pub fn isExported(self: Fn, tree: *ast.Tree) bool { + if (self.fn_proto.extern_export_inline_token) |tok_index| { + const token = tree.tokens.at(tok_index); + return token.id == Token.Id.Keyword_export; + } else { + return false; + } + } + }; + + pub const CompTime = struct { + base: Decl, + }; +}; + diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 3334d9511b..f517dfe579 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -1,111 +1,656 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Module = @import("module.zig").Module; const Scope = @import("scope.zig").Scope; +const ast = std.zig.ast; +const Allocator = std.mem.Allocator; +const Value = @import("value.zig").Value; +const Type = Value.Type; +const assert = std.debug.assert; +const Token = std.zig.Token; +const ParsedFile = @import("parsed_file.zig").ParsedFile; + +pub const LVal = enum { + None, + Ptr, +}; + +pub const Mut = enum { + Mut, + Const, +}; + +pub const Volatility = enum { + NonVolatile, + Volatile, +}; + +pub const IrVal = union(enum) { + Unknown, + Known: *Value, + + pub fn dump(self: IrVal) void { + switch (self) { + IrVal.Unknown => std.debug.warn("Unknown"), + IrVal.Known => |value| { + std.debug.warn("Known("); + value.dump(); + std.debug.warn(")"); + }, + } + } +}; pub const Instruction = struct { id: Id, scope: *Scope, + debug_id: usize, + val: IrVal, + + /// true if this instruction was generated by zig and not from user code + is_generated: bool, + + pub fn cast(base: *Instruction, comptime T: type) ?*T { + if (base.id == comptime typeToId(T)) { + return @fieldParentPtr(T, "base", base); + } + return null; + } + + pub fn typeToId(comptime T: type) Id { + comptime var i = 0; + inline while (i < @memberCount(Id)) : (i += 1) { + if (T == @field(Instruction, @memberName(Id, i))) { + return @field(Id, @memberName(Id, i)); + } + } + unreachable; + } + + pub fn dump(base: *const Instruction) void { + comptime var i = 0; + inline while (i < @memberCount(Id)) : (i += 1) { + if (base.id == @field(Id, @memberName(Id, i))) { + const T = @field(Instruction, @memberName(Id, i)); + std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id)); + @fieldParentPtr(T, "base", base).dump(); + std.debug.warn(")"); + return; + } + } + unreachable; + } + + pub fn setGenerated(base: *Instruction) void { + base.is_generated = true; + } + + pub fn isNoReturn(base: *const Instruction) bool { + switch (base.val) { + IrVal.Unknown => return false, + IrVal.Known => |x| return x.typeof.id == Type.Id.NoReturn, + } + } pub const Id = enum { - Br, - CondBr, - SwitchBr, - SwitchVar, - SwitchTarget, - Phi, - UnOp, - BinOp, - DeclVar, - LoadPtr, - StorePtr, - FieldPtr, - StructFieldPtr, - UnionFieldPtr, - ElemPtr, - VarPtr, - Call, - Const, Return, - Cast, - ContainerInitList, - ContainerInitFields, - StructInit, - UnionInit, - Unreachable, - TypeOf, - ToPtrType, - PtrTypeChild, - SetRuntimeSafety, - SetFloatMode, - ArrayType, - SliceType, - Asm, - SizeOf, - TestNonNull, - UnwrapMaybe, - MaybeWrap, - UnionTag, - Clz, - Ctz, - Import, - CImport, - CInclude, - CDefine, - CUndef, - ArrayLen, + Const, Ref, - MinValue, - MaxValue, - CompileErr, - CompileLog, - ErrName, - EmbedFile, - Cmpxchg, - Fence, - Truncate, - IntType, - BoolNot, - Memset, - Memcpy, - Slice, - MemberCount, - MemberType, - MemberName, - Breakpoint, - ReturnAddress, - FrameAddress, - AlignOf, - OverflowOp, - TestErr, - UnwrapErrCode, - UnwrapErrPayload, - ErrWrapCode, - ErrWrapPayload, - FnProto, - TestComptime, - PtrCast, - BitCast, - WidenOrShorten, - IntToPtr, - PtrToInt, - IntToEnum, - IntToErr, - ErrToInt, - CheckSwitchProngs, - CheckStatementIsVoid, - TypeName, - CanImplicitCast, - DeclRef, - Panic, - TagName, - TagType, - FieldParentPtr, - OffsetOf, - TypeId, - SetEvalBranchQuota, - PtrTypeOf, - AlignCast, - OpaqueType, - SetAlignStack, - ArgType, - Export, + DeclVar, + CheckVoidStmt, + Phi, + Br, + }; + + pub const Const = struct { + base: Instruction, + + pub fn buildBool(irb: *Builder, scope: *Scope, val: bool) !*Instruction { + const inst = try irb.arena().create(Const{ + .base = Instruction{ + .id = Instruction.Id.Const, + .is_generated = false, + .scope = scope, + .debug_id = irb.next_debug_id, + .val = IrVal{ .Known = &Value.Bool.get(irb.module, val).base }, + }, + }); + irb.next_debug_id += 1; + try irb.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + pub fn buildVoid(irb: *Builder, scope: *Scope, is_generated: bool) !*Instruction { + const inst = try irb.arena().create(Const{ + .base = Instruction{ + .id = Instruction.Id.Const, + .is_generated = is_generated, + .scope = scope, + .debug_id = irb.next_debug_id, + .val = IrVal{ .Known = &Value.Void.get(irb.module).base }, + }, + }); + irb.next_debug_id += 1; + try irb.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + pub fn dump(inst: *const Const) void { + inst.base.val.Known.dump(); + } + }; + + pub const Return = struct { + base: Instruction, + return_value: *Instruction, + + pub fn build(irb: *Builder, scope: *Scope, return_value: *Instruction) !*Instruction { + const inst = try irb.arena().create(Return{ + .base = Instruction{ + .id = Instruction.Id.Return, + .is_generated = false, + .scope = scope, + .debug_id = irb.next_debug_id, + .val = IrVal{ .Known = &Value.Void.get(irb.module).base }, + }, + .return_value = return_value, + }); + irb.next_debug_id += 1; + try irb.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + pub fn dump(inst: *const Return) void { + std.debug.warn("#{}", inst.return_value.debug_id); + } + }; + + pub const Ref = struct { + base: Instruction, + target: *Instruction, + mut: Mut, + volatility: Volatility, + + pub fn build( + irb: *Builder, + scope: *Scope, + target: *Instruction, + mut: Mut, + volatility: Volatility, + ) !*Instruction { + const inst = try irb.arena().create(Ref{ + .base = Instruction{ + .id = Instruction.Id.Ref, + .is_generated = false, + .scope = scope, + .debug_id = irb.next_debug_id, + .val = IrVal.Unknown, + }, + .target = target, + .mut = mut, + .volatility = volatility, + }); + irb.next_debug_id += 1; + try irb.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + pub fn dump(inst: *const Ref) void {} + }; + + pub const DeclVar = struct { + base: Instruction, + variable: *Variable, + + pub fn dump(inst: *const DeclVar) void {} + }; + + pub const CheckVoidStmt = struct { + base: Instruction, + target: *Instruction, + + pub fn build( + irb: *Builder, + scope: *Scope, + target: *Instruction, + ) !*Instruction { + const inst = try irb.arena().create(CheckVoidStmt{ + .base = Instruction{ + .id = Instruction.Id.CheckVoidStmt, + .is_generated = true, + .scope = scope, + .debug_id = irb.next_debug_id, + .val = IrVal{ .Known = &Value.Void.get(irb.module).base }, + }, + .target = target, + }); + irb.next_debug_id += 1; + try irb.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + pub fn dump(inst: *const CheckVoidStmt) void {} + }; + + pub const Phi = struct { + base: Instruction, + incoming_blocks: []*BasicBlock, + incoming_values: []*Instruction, + + pub fn build( + irb: *Builder, + scope: *Scope, + incoming_blocks: []*BasicBlock, + incoming_values: []*Instruction, + ) !*Instruction { + const inst = try irb.arena().create(Phi{ + .base = Instruction{ + .id = Instruction.Id.Phi, + .is_generated = false, + .scope = scope, + .debug_id = irb.next_debug_id, + .val = IrVal.Unknown, + }, + .incoming_blocks = incoming_blocks, + .incoming_values = incoming_values, + }); + irb.next_debug_id += 1; + try irb.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + pub fn dump(inst: *const Phi) void {} + }; + + pub const Br = struct { + base: Instruction, + dest_block: *BasicBlock, + is_comptime: *Instruction, + + pub fn build( + irb: *Builder, + scope: *Scope, + dest_block: *BasicBlock, + is_comptime: *Instruction, + ) !*Instruction { + const inst = try irb.arena().create(Br{ + .base = Instruction{ + .id = Instruction.Id.Br, + .is_generated = false, + .scope = scope, + .debug_id = irb.next_debug_id, + .val = IrVal{ .Known = &Value.NoReturn.get(irb.module).base }, + }, + .dest_block = dest_block, + .is_comptime = is_comptime, + }); + irb.next_debug_id += 1; + try irb.current_basic_block.instruction_list.append(&inst.base); + return &inst.base; + } + + pub fn dump(inst: *const Br) void {} }; }; + +pub const Variable = struct { + child_scope: *Scope, +}; + +pub const BasicBlock = struct { + ref_count: usize, + name_hint: []const u8, + debug_id: usize, + scope: *Scope, + instruction_list: std.ArrayList(*Instruction), + + pub fn ref(self: *BasicBlock) void { + self.ref_count += 1; + } +}; + +/// Stuff that survives longer than Builder +pub const Code = struct { + basic_block_list: std.ArrayList(*BasicBlock), + arena: std.heap.ArenaAllocator, + + /// allocator is module.a() + pub fn destroy(self: *Code, allocator: *Allocator) void { + self.arena.deinit(); + allocator.destroy(self); + } + + pub fn dump(self: *Code) void { + var bb_i: usize = 0; + for (self.basic_block_list.toSliceConst()) |bb| { + std.debug.warn("{}_{}:\n", bb.name_hint, bb.debug_id); + for (bb.instruction_list.toSliceConst()) |instr| { + std.debug.warn(" "); + instr.dump(); + std.debug.warn("\n"); + } + } + } +}; + +pub const Builder = struct { + module: *Module, + code: *Code, + current_basic_block: *BasicBlock, + next_debug_id: usize, + parsed_file: *ParsedFile, + is_comptime: bool, + + pub const Error = error{ + OutOfMemory, + Unimplemented, + }; + + pub fn init(module: *Module, parsed_file: *ParsedFile) !Builder { + const code = try module.a().create(Code{ + .basic_block_list = undefined, + .arena = std.heap.ArenaAllocator.init(module.a()), + }); + code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator); + errdefer code.destroy(module.a()); + + return Builder{ + .module = module, + .parsed_file = parsed_file, + .current_basic_block = undefined, + .code = code, + .next_debug_id = 0, + .is_comptime = false, + }; + } + + pub fn abort(self: *Builder) void { + self.code.destroy(self.module.a()); + } + + /// Call code.destroy() when done + pub fn finish(self: *Builder) *Code { + return self.code; + } + + /// No need to clean up resources thanks to the arena allocator. + pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: []const u8) !*BasicBlock { + const basic_block = try self.arena().create(BasicBlock{ + .ref_count = 0, + .name_hint = name_hint, + .debug_id = self.next_debug_id, + .scope = scope, + .instruction_list = std.ArrayList(*Instruction).init(self.arena()), + }); + self.next_debug_id += 1; + return basic_block; + } + + pub fn setCursorAtEndAndAppendBlock(self: *Builder, basic_block: *BasicBlock) !void { + try self.code.basic_block_list.append(basic_block); + self.setCursorAtEnd(basic_block); + } + + pub fn setCursorAtEnd(self: *Builder, basic_block: *BasicBlock) void { + self.current_basic_block = basic_block; + } + + pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Instruction { + switch (node.id) { + ast.Node.Id.Root => unreachable, + ast.Node.Id.Use => unreachable, + ast.Node.Id.TestDecl => unreachable, + ast.Node.Id.VarDecl => @panic("TODO"), + ast.Node.Id.Defer => @panic("TODO"), + ast.Node.Id.InfixOp => @panic("TODO"), + ast.Node.Id.PrefixOp => @panic("TODO"), + ast.Node.Id.SuffixOp => @panic("TODO"), + ast.Node.Id.Switch => @panic("TODO"), + ast.Node.Id.While => @panic("TODO"), + ast.Node.Id.For => @panic("TODO"), + ast.Node.Id.If => @panic("TODO"), + ast.Node.Id.ControlFlowExpression => return error.Unimplemented, + ast.Node.Id.Suspend => @panic("TODO"), + ast.Node.Id.VarType => @panic("TODO"), + ast.Node.Id.ErrorType => @panic("TODO"), + ast.Node.Id.FnProto => @panic("TODO"), + ast.Node.Id.PromiseType => @panic("TODO"), + ast.Node.Id.IntegerLiteral => @panic("TODO"), + ast.Node.Id.FloatLiteral => @panic("TODO"), + ast.Node.Id.StringLiteral => @panic("TODO"), + ast.Node.Id.MultilineStringLiteral => @panic("TODO"), + ast.Node.Id.CharLiteral => @panic("TODO"), + ast.Node.Id.BoolLiteral => @panic("TODO"), + ast.Node.Id.NullLiteral => @panic("TODO"), + ast.Node.Id.UndefinedLiteral => @panic("TODO"), + ast.Node.Id.ThisLiteral => @panic("TODO"), + ast.Node.Id.Unreachable => @panic("TODO"), + ast.Node.Id.Identifier => @panic("TODO"), + ast.Node.Id.GroupedExpression => { + const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node); + return irb.genNode(grouped_expr.expr, scope, lval); + }, + ast.Node.Id.BuiltinCall => @panic("TODO"), + ast.Node.Id.ErrorSetDecl => @panic("TODO"), + ast.Node.Id.ContainerDecl => @panic("TODO"), + ast.Node.Id.Asm => @panic("TODO"), + ast.Node.Id.Comptime => @panic("TODO"), + ast.Node.Id.Block => { + const block = @fieldParentPtr(ast.Node.Block, "base", node); + return irb.lvalWrap(scope, try irb.genBlock(block, scope), lval); + }, + ast.Node.Id.DocComment => @panic("TODO"), + ast.Node.Id.SwitchCase => @panic("TODO"), + ast.Node.Id.SwitchElse => @panic("TODO"), + ast.Node.Id.Else => @panic("TODO"), + ast.Node.Id.Payload => @panic("TODO"), + ast.Node.Id.PointerPayload => @panic("TODO"), + ast.Node.Id.PointerIndexPayload => @panic("TODO"), + ast.Node.Id.StructField => @panic("TODO"), + ast.Node.Id.UnionTag => @panic("TODO"), + ast.Node.Id.EnumTag => @panic("TODO"), + ast.Node.Id.ErrorTag => @panic("TODO"), + ast.Node.Id.AsmInput => @panic("TODO"), + ast.Node.Id.AsmOutput => @panic("TODO"), + ast.Node.Id.AsyncAttribute => @panic("TODO"), + ast.Node.Id.ParamDecl => @panic("TODO"), + ast.Node.Id.FieldInitializer => @panic("TODO"), + } + } + + fn isCompTime(irb: *Builder, target_scope: *Scope) bool { + if (irb.is_comptime) + return true; + + var scope = target_scope; + while (true) { + switch (scope.id) { + Scope.Id.CompTime => return true, + Scope.Id.FnDef => return false, + Scope.Id.Decls => unreachable, + Scope.Id.Block, + Scope.Id.Defer, + Scope.Id.DeferExpr, + => scope = scope.parent orelse return false, + } + } + } + + pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Instruction { + const block_scope = try Scope.Block.create(irb.module, parent_scope); + + const outer_block_scope = &block_scope.base; + var child_scope = outer_block_scope; + + if (parent_scope.findFnDef()) |fndef_scope| { + if (fndef_scope.fn_val.child_scope == parent_scope) { + fndef_scope.fn_val.block_scope = block_scope; + } + } + + if (block.statements.len == 0) { + // {} + return Instruction.Const.buildVoid(irb, child_scope, false); + } + + if (block.label) |label| { + block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena()); + block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena()); + block_scope.end_block = try irb.createBasicBlock(parent_scope, "BlockEnd"); + block_scope.is_comptime = try Instruction.Const.buildBool(irb, parent_scope, irb.isCompTime(parent_scope)); + } + + var is_continuation_unreachable = false; + var noreturn_return_value: ?*Instruction = null; + + var stmt_it = block.statements.iterator(0); + while (stmt_it.next()) |statement_node_ptr| { + const statement_node = statement_node_ptr.*; + + if (statement_node.cast(ast.Node.Defer)) |defer_node| { + // defer starts a new scope + const defer_token = irb.parsed_file.tree.tokens.at(defer_node.defer_token); + const kind = switch (defer_token.id) { + Token.Id.Keyword_defer => Scope.Defer.Kind.ScopeExit, + Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit, + else => unreachable, + }; + const defer_expr_scope = try Scope.DeferExpr.create(irb.module, parent_scope, defer_node.expr); + const defer_child_scope = try Scope.Defer.create(irb.module, parent_scope, kind, defer_expr_scope); + child_scope = &defer_child_scope.base; + continue; + } + const statement_value = try irb.genNode(statement_node, child_scope, LVal.None); + + is_continuation_unreachable = statement_value.isNoReturn(); + if (is_continuation_unreachable) { + // keep the last noreturn statement value around in case we need to return it + noreturn_return_value = statement_value; + } + + if (statement_value.cast(Instruction.DeclVar)) |decl_var| { + // variable declarations start a new scope + child_scope = decl_var.variable.child_scope; + } else if (!is_continuation_unreachable) { + // this statement's value must be void + _ = Instruction.CheckVoidStmt.build(irb, child_scope, statement_value); + } + } + + if (is_continuation_unreachable) { + assert(noreturn_return_value != null); + if (block.label == null or block_scope.incoming_blocks.len == 0) { + return noreturn_return_value.?; + } + + try irb.setCursorAtEndAndAppendBlock(block_scope.end_block); + return Instruction.Phi.build( + irb, + parent_scope, + block_scope.incoming_blocks.toOwnedSlice(), + block_scope.incoming_values.toOwnedSlice(), + ); + } + + if (block.label) |label| { + try block_scope.incoming_blocks.append(irb.current_basic_block); + try block_scope.incoming_values.append( + try Instruction.Const.buildVoid(irb, parent_scope, true), + ); + _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit); + (try Instruction.Br.build( + irb, + parent_scope, + block_scope.end_block, + block_scope.is_comptime, + )).setGenerated(); + try irb.setCursorAtEndAndAppendBlock(block_scope.end_block); + return Instruction.Phi.build( + irb, + parent_scope, + block_scope.incoming_blocks.toOwnedSlice(), + block_scope.incoming_values.toOwnedSlice(), + ); + } + + _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit); + const result = try Instruction.Const.buildVoid(irb, child_scope, false); + result.setGenerated(); + return result; + } + + fn genDefersForBlock( + irb: *Builder, + inner_scope: *Scope, + outer_scope: *Scope, + gen_kind: Scope.Defer.Kind, + ) !bool { + var scope = inner_scope; + var is_noreturn = false; + while (true) { + switch (scope.id) { + Scope.Id.Defer => { + const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope); + const generate = switch (defer_scope.kind) { + Scope.Defer.Kind.ScopeExit => true, + Scope.Defer.Kind.ErrorExit => gen_kind == Scope.Defer.Kind.ErrorExit, + }; + if (generate) { + const defer_expr_scope = defer_scope.defer_expr_scope; + const instruction = try irb.genNode( + defer_expr_scope.expr_node, + &defer_expr_scope.base, + LVal.None, + ); + if (instruction.isNoReturn()) { + is_noreturn = true; + } else { + _ = Instruction.CheckVoidStmt.build(irb, &defer_expr_scope.base, instruction); + } + } + }, + Scope.Id.FnDef, + Scope.Id.Decls, + => return is_noreturn, + + Scope.Id.CompTime, + Scope.Id.Block, + => scope = scope.parent orelse return is_noreturn, + + Scope.Id.DeferExpr => unreachable, + } + } + } + + pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Instruction, lval: LVal) !*Instruction { + switch (lval) { + LVal.None => return instruction, + LVal.Ptr => { + // We needed a pointer to a value, but we got a value. So we create + // an instruction which just makes a const pointer of it. + return Instruction.Ref.build(irb, scope, instruction, Mut.Const, Volatility.NonVolatile); + }, + } + } + + fn arena(self: *Builder) *Allocator { + return &self.code.arena.allocator; + } +}; + +pub async fn gen(module: *Module, body_node: *ast.Node, scope: *Scope, parsed_file: *ParsedFile) !*Code { + var irb = try Builder.init(module, parsed_file); + errdefer irb.abort(); + + const entry_block = try irb.createBasicBlock(scope, "Entry"); + entry_block.ref(); // Entry block gets a reference because we enter it to begin. + try irb.setCursorAtEndAndAppendBlock(entry_block); + + const result = try irb.genNode(body_node, scope, LVal.None); + if (!result.isNoReturn()) { + const void_inst = try Instruction.Const.buildVoid(&irb, scope, false); + (try Instruction.Return.build(&irb, scope, void_inst)).setGenerated(); + } + + return irb.finish(); +} diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig index 5cde12f65c..e74c84e02c 100644 --- a/src-self-hosted/module.zig +++ b/src-self-hosted/module.zig @@ -15,12 +15,21 @@ const errmsg = @import("errmsg.zig"); const ast = std.zig.ast; const event = std.event; const assert = std.debug.assert; +const AtomicRmwOp = builtin.AtomicRmwOp; +const AtomicOrder = builtin.AtomicOrder; +const Scope = @import("scope.zig").Scope; +const Decl = @import("decl.zig").Decl; +const ir = @import("ir.zig"); +const Visib = @import("visib.zig").Visib; +const ParsedFile = @import("parsed_file.zig").ParsedFile; +const Value = @import("value.zig").Value; +const Type = Value.Type; pub const Module = struct { loop: *event.Loop, name: Buffer, root_src_path: ?[]const u8, - module: llvm.ModuleRef, + llvm_module: llvm.ModuleRef, context: llvm.ContextRef, builder: llvm.BuilderRef, target: Target, @@ -91,6 +100,16 @@ pub const Module = struct { compile_errors: event.Locked(CompileErrList), + meta_type: *Type.MetaType, + void_type: *Type.Void, + bool_type: *Type.Bool, + noreturn_type: *Type.NoReturn, + + void_value: *Value.Void, + true_value: *Value.Bool, + false_value: *Value.Bool, + noreturn_value: *Value.NoReturn, + const CompileErrList = std.ArrayList(*errmsg.Msg); // TODO handle some of these earlier and report them in a way other than error codes @@ -129,6 +148,7 @@ pub const Module = struct { Overflow, NotSupported, BufferTooSmall, + Unimplemented, }; pub const Event = union(enum) { @@ -180,8 +200,8 @@ pub const Module = struct { const context = c.LLVMContextCreate() orelse return error.OutOfMemory; errdefer c.LLVMContextDispose(context); - const module = c.LLVMModuleCreateWithNameInContext(name_buffer.ptr(), context) orelse return error.OutOfMemory; - errdefer c.LLVMDisposeModule(module); + const llvm_module = c.LLVMModuleCreateWithNameInContext(name_buffer.ptr(), context) orelse return error.OutOfMemory; + errdefer c.LLVMDisposeModule(llvm_module); const builder = c.LLVMCreateBuilderInContext(context) orelse return error.OutOfMemory; errdefer c.LLVMDisposeBuilder(builder); @@ -189,12 +209,12 @@ pub const Module = struct { const events = try event.Channel(Event).create(loop, 0); errdefer events.destroy(); - return loop.allocator.create(Module{ + const module = try loop.allocator.create(Module{ .loop = loop, .events = events, .name = name_buffer, .root_src_path = root_src_path, - .module = module, + .llvm_module = llvm_module, .context = context, .builder = builder, .target = target.*, @@ -248,7 +268,109 @@ pub const Module = struct { .exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)), .build_group = event.Group(BuildError!void).init(loop), .compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)), + + .meta_type = undefined, + .void_type = undefined, + .void_value = undefined, + .bool_type = undefined, + .true_value = undefined, + .false_value = undefined, + .noreturn_type = undefined, + .noreturn_value = undefined, }); + try module.initTypes(); + return module; + } + + fn initTypes(module: *Module) !void { + module.meta_type = try module.a().create(Type.MetaType{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = undefined, + .ref_count = 3, // 3 because it references itself twice + }, + .id = builtin.TypeId.Type, + }, + .value = undefined, + }); + module.meta_type.value = &module.meta_type.base; + module.meta_type.base.base.typeof = &module.meta_type.base; + errdefer module.a().destroy(module.meta_type); + + module.void_type = try module.a().create(Type.Void{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = &Type.MetaType.get(module).base, + .ref_count = 1, + }, + .id = builtin.TypeId.Void, + }, + }); + errdefer module.a().destroy(module.void_type); + + module.noreturn_type = try module.a().create(Type.NoReturn{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = &Type.MetaType.get(module).base, + .ref_count = 1, + }, + .id = builtin.TypeId.NoReturn, + }, + }); + errdefer module.a().destroy(module.noreturn_type); + + module.bool_type = try module.a().create(Type.Bool{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = &Type.MetaType.get(module).base, + .ref_count = 1, + }, + .id = builtin.TypeId.Bool, + }, + }); + errdefer module.a().destroy(module.bool_type); + + module.void_value = try module.a().create(Value.Void{ + .base = Value{ + .id = Value.Id.Void, + .typeof = &Type.Void.get(module).base, + .ref_count = 1, + }, + }); + errdefer module.a().destroy(module.void_value); + + module.true_value = try module.a().create(Value.Bool{ + .base = Value{ + .id = Value.Id.Bool, + .typeof = &Type.Bool.get(module).base, + .ref_count = 1, + }, + .x = true, + }); + errdefer module.a().destroy(module.true_value); + + module.false_value = try module.a().create(Value.Bool{ + .base = Value{ + .id = Value.Id.Bool, + .typeof = &Type.Bool.get(module).base, + .ref_count = 1, + }, + .x = false, + }); + errdefer module.a().destroy(module.false_value); + + module.noreturn_value = try module.a().create(Value.NoReturn{ + .base = Value{ + .id = Value.Id.NoReturn, + .typeof = &Type.NoReturn.get(module).base, + .ref_count = 1, + }, + }); + errdefer module.a().destroy(module.noreturn_value); } fn dump(self: *Module) void { @@ -256,9 +378,17 @@ pub const Module = struct { } pub fn destroy(self: *Module) void { + self.noreturn_value.base.deref(self); + self.void_value.base.deref(self); + self.false_value.base.deref(self); + self.true_value.base.deref(self); + self.noreturn_type.base.base.deref(self); + self.void_type.base.base.deref(self); + self.meta_type.base.base.deref(self); + self.events.destroy(); c.LLVMDisposeBuilder(self.builder); - c.LLVMDisposeModule(self.module); + c.LLVMDisposeModule(self.llvm_module); c.LLVMContextDispose(self.context); self.name.deinit(); @@ -331,8 +461,8 @@ pub const Module = struct { const tree = &parsed_file.tree; // create empty struct for it - const decls = try Scope.Decls.create(self.a(), null); - errdefer decls.destroy(); + const decls = try Scope.Decls.create(self, null); + defer decls.base.deref(self); var decl_group = event.Group(BuildError!void).init(self.loop); errdefer decl_group.cancelAll(); @@ -359,14 +489,17 @@ pub const Module = struct { .id = Decl.Id.Fn, .name = name, .visib = parseVisibToken(tree, fn_proto.visib_token), - .resolution = Decl.Resolution.Unresolved, + .resolution = event.Future(BuildError!void).init(self.loop), + .resolution_in_progress = 0, + .parsed_file = parsed_file, + .parent_scope = &decls.base, }, .value = Decl.Fn.Val{ .Unresolved = {} }, .fn_proto = fn_proto, }); errdefer self.a().destroy(fn_decl); - try decl_group.call(addTopLevelDecl, self, parsed_file, &fn_decl.base); + try decl_group.call(addTopLevelDecl, self, &fn_decl.base); }, ast.Node.Id.TestDecl => @panic("TODO"), else => unreachable, @@ -376,12 +509,12 @@ pub const Module = struct { try await (async self.build_group.wait() catch unreachable); } - async fn addTopLevelDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void { - const is_export = decl.isExported(&parsed_file.tree); + async fn addTopLevelDecl(self: *Module, decl: *Decl) !void { + const is_export = decl.isExported(&decl.parsed_file.tree); if (is_export) { - try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl); - try self.build_group.call(generateDecl, self, parsed_file, decl); + try self.build_group.call(verifyUniqueSymbol, self, decl); + try self.build_group.call(resolveDecl, self, decl); } } @@ -416,36 +549,21 @@ pub const Module = struct { try compile_errors.value.append(msg); } - async fn verifyUniqueSymbol(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void { + async fn verifyUniqueSymbol(self: *Module, decl: *Decl) !void { const exported_symbol_names = await (async self.exported_symbol_names.acquire() catch unreachable); defer exported_symbol_names.release(); if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| { try self.addCompileError( - parsed_file, + decl.parsed_file, decl.getSpan(), "exported symbol collision: '{}'", decl.name, ); + // TODO add error note showing location of other symbol } } - /// This declaration has been blessed as going into the final code generation. - async fn generateDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) void { - switch (decl.id) { - Decl.Id.Var => @panic("TODO"), - Decl.Id.Fn => { - const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl); - return await (async self.generateDeclFn(parsed_file, fn_decl) catch unreachable); - }, - Decl.Id.CompTime => @panic("TODO"), - } - } - - async fn generateDeclFn(self: *Module, parsed_file: *ParsedFile, fn_decl: *Decl.Fn) void { - fn_decl.value = Decl.Fn.Val{ .Ok = Value.Fn{} }; - } - pub fn link(self: *Module, out_file: ?[]const u8) !void { warn("TODO link"); return error.Todo; @@ -501,177 +619,48 @@ fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib } } -pub const Scope = struct { - id: Id, - parent: ?*Scope, - - pub const Id = enum { - Decls, - Block, - }; - - pub const Decls = struct { - base: Scope, - table: Decl.Table, - - pub fn create(a: *Allocator, parent: ?*Scope) !*Decls { - const self = try a.create(Decls{ - .base = Scope{ - .id = Id.Decls, - .parent = parent, - }, - .table = undefined, - }); - errdefer a.destroy(self); - - self.table = Decl.Table.init(a); - errdefer self.table.deinit(); - - return self; - } - - pub fn destroy(self: *Decls) void { - self.table.deinit(); - self.table.allocator.destroy(self); - self.* = undefined; - } - }; - - pub const Block = struct { - base: Scope, - }; -}; - -pub const Visib = enum { - Private, - Pub, -}; - -pub const Decl = struct { - id: Id, - name: []const u8, - visib: Visib, - resolution: Resolution, - - pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8); - - pub fn isExported(base: *const Decl, tree: *ast.Tree) bool { - switch (base.id) { - Id.Fn => { - const fn_decl = @fieldParentPtr(Fn, "base", base); - return fn_decl.isExported(tree); - }, - else => return false, - } +/// This declaration has been blessed as going into the final code generation. +pub async fn resolveDecl(module: *Module, decl: *Decl) !void { + if (@atomicRmw(u8, &decl.resolution_in_progress, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) { + decl.resolution.data = await (async generateDecl(module, decl) catch unreachable); + decl.resolution.resolve(); + } else { + return (await (async decl.resolution.get() catch unreachable)).*; } +} - pub fn getSpan(base: *const Decl) errmsg.Span { - switch (base.id) { - Id.Fn => { - const fn_decl = @fieldParentPtr(Fn, "base", base); - const fn_proto = fn_decl.fn_proto; - const start = fn_proto.fn_token; - const end = fn_proto.name_token orelse start; - return errmsg.Span{ - .first = start, - .last = end + 1, - }; - }, - else => @panic("TODO"), - } +/// The function that actually does the generation. +async fn generateDecl(module: *Module, decl: *Decl) !void { + switch (decl.id) { + Decl.Id.Var => @panic("TODO"), + Decl.Id.Fn => { + const fn_decl = @fieldParentPtr(Decl.Fn, "base", decl); + return await (async generateDeclFn(module, fn_decl) catch unreachable); + }, + Decl.Id.CompTime => @panic("TODO"), } +} - pub const Resolution = enum { - Unresolved, - InProgress, - Invalid, - Ok, - }; +async fn generateDeclFn(module: *Module, fn_decl: *Decl.Fn) !void { + const body_node = fn_decl.fn_proto.body_node orelse @panic("TODO extern fn proto decl"); - pub const Id = enum { - Var, - Fn, - CompTime, - }; + const fndef_scope = try Scope.FnDef.create(module, fn_decl.base.parent_scope); + defer fndef_scope.base.deref(module); - pub const Var = struct { - base: Decl, - }; + const fn_type = try Type.Fn.create(module); + defer fn_type.base.base.deref(module); - pub const Fn = struct { - base: Decl, - value: Val, - fn_proto: *const ast.Node.FnProto, + const fn_val = try Value.Fn.create(module, fn_type, fndef_scope); + defer fn_val.base.deref(module); - // TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous - pub const Val = union { - Unresolved: void, - Ok: Value.Fn, - }; + fn_decl.value = Decl.Fn.Val{ .Ok = fn_val }; - pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 { - return if (self.fn_proto.extern_export_inline_token) |tok_index| x: { - const token = tree.tokens.at(tok_index); - break :x switch (token.id) { - Token.Id.Extern => tree.tokenSlicePtr(token), - else => null, - }; - } else null; - } - - pub fn isExported(self: Fn, tree: *ast.Tree) bool { - if (self.fn_proto.extern_export_inline_token) |tok_index| { - const token = tree.tokens.at(tok_index); - return token.id == Token.Id.Keyword_export; - } else { - return false; - } - } - }; - - pub const CompTime = struct { - base: Decl, - }; -}; - -pub const Value = struct { - pub const Fn = struct {}; -}; - -pub const Type = struct { - id: Id, - - pub const Id = enum { - Type, - Void, - Bool, - NoReturn, - Int, - Float, - Pointer, - Array, - Struct, - ComptimeFloat, - ComptimeInt, - Undefined, - Null, - Optional, - ErrorUnion, - ErrorSet, - Enum, - Union, - Fn, - Opaque, - Promise, - }; - - pub const Struct = struct { - base: Type, - decls: *Scope.Decls, - }; -}; - -pub const ParsedFile = struct { - tree: ast.Tree, - realpath: []const u8, -}; + const code = try await (async ir.gen( + module, + body_node, + &fndef_scope.base, + fn_decl.base.parsed_file, + ) catch unreachable); + //code.dump(); + //try await (async irAnalyze(module, func) catch unreachable); +} diff --git a/src-self-hosted/parsed_file.zig b/src-self-hosted/parsed_file.zig new file mode 100644 index 0000000000..d728c2fd18 --- /dev/null +++ b/src-self-hosted/parsed_file.zig @@ -0,0 +1,6 @@ +const ast = @import("std").zig.ast; + +pub const ParsedFile = struct { + tree: ast.Tree, + realpath: []const u8, +}; diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig index b73dcb4ed3..8f8d016a7c 100644 --- a/src-self-hosted/scope.zig +++ b/src-self-hosted/scope.zig @@ -1,16 +1,234 @@ +const std = @import("std"); +const Allocator = mem.Allocator; +const Decl = @import("decl.zig").Decl; +const Module = @import("module.zig").Module; +const mem = std.mem; +const ast = std.zig.ast; +const Value = @import("value.zig").Value; +const ir = @import("ir.zig"); + pub const Scope = struct { id: Id, - parent: *Scope, + parent: ?*Scope, + ref_count: usize, + + pub fn ref(base: *Scope) void { + base.ref_count += 1; + } + + pub fn deref(base: *Scope, module: *Module) void { + base.ref_count -= 1; + if (base.ref_count == 0) { + if (base.parent) |parent| parent.deref(module); + switch (base.id) { + Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(), + Id.Block => @fieldParentPtr(Block, "base", base).destroy(module), + Id.FnDef => @fieldParentPtr(FnDef, "base", base).destroy(module), + Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(module), + Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(module), + Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(module), + } + } + } + + pub fn findFnDef(base: *Scope) ?*FnDef { + var scope = base; + while (true) { + switch (scope.id) { + Id.FnDef => return @fieldParentPtr(FnDef, "base", base), + Id.Decls => return null, + + Id.Block, + Id.Defer, + Id.DeferExpr, + Id.CompTime, + => scope = scope.parent orelse return null, + } + } + } pub const Id = enum { Decls, Block, - Defer, - DeferExpr, - VarDecl, - CImport, - Loop, FnDef, CompTime, + Defer, + DeferExpr, + }; + + pub const Decls = struct { + base: Scope, + table: Decl.Table, + + /// Creates a Decls scope with 1 reference + pub fn create(module: *Module, parent: ?*Scope) !*Decls { + const self = try module.a().create(Decls{ + .base = Scope{ + .id = Id.Decls, + .parent = parent, + .ref_count = 1, + }, + .table = undefined, + }); + errdefer module.a().destroy(self); + + self.table = Decl.Table.init(module.a()); + errdefer self.table.deinit(); + + if (parent) |p| p.ref(); + + return self; + } + + pub fn destroy(self: *Decls) void { + self.table.deinit(); + self.table.allocator.destroy(self); + } + }; + + pub const Block = struct { + base: Scope, + incoming_values: std.ArrayList(*ir.Instruction), + incoming_blocks: std.ArrayList(*ir.BasicBlock), + end_block: *ir.BasicBlock, + is_comptime: *ir.Instruction, + + /// Creates a Block scope with 1 reference + pub fn create(module: *Module, parent: ?*Scope) !*Block { + const self = try module.a().create(Block{ + .base = Scope{ + .id = Id.Block, + .parent = parent, + .ref_count = 1, + }, + .incoming_values = undefined, + .incoming_blocks = undefined, + .end_block = undefined, + .is_comptime = undefined, + }); + errdefer module.a().destroy(self); + + if (parent) |p| p.ref(); + return self; + } + + pub fn destroy(self: *Block, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const FnDef = struct { + base: Scope, + + /// This reference is not counted so that the scope can get destroyed with the function + fn_val: *Value.Fn, + + /// Creates a FnDef scope with 1 reference + /// Must set the fn_val later + pub fn create(module: *Module, parent: ?*Scope) !*FnDef { + const self = try module.a().create(FnDef{ + .base = Scope{ + .id = Id.FnDef, + .parent = parent, + .ref_count = 1, + }, + .fn_val = undefined, + }); + + if (parent) |p| p.ref(); + + return self; + } + + pub fn destroy(self: *FnDef, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const CompTime = struct { + base: Scope, + + /// Creates a CompTime scope with 1 reference + pub fn create(module: *Module, parent: ?*Scope) !*CompTime { + const self = try module.a().create(CompTime{ + .base = Scope{ + .id = Id.CompTime, + .parent = parent, + .ref_count = 1, + }, + }); + + if (parent) |p| p.ref(); + return self; + } + + pub fn destroy(self: *CompTime, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Defer = struct { + base: Scope, + defer_expr_scope: *DeferExpr, + kind: Kind, + + pub const Kind = enum { + ScopeExit, + ErrorExit, + }; + + /// Creates a Defer scope with 1 reference + pub fn create( + module: *Module, + parent: ?*Scope, + kind: Kind, + defer_expr_scope: *DeferExpr, + ) !*Defer { + const self = try module.a().create(Defer{ + .base = Scope{ + .id = Id.Defer, + .parent = parent, + .ref_count = 1, + }, + .defer_expr_scope = defer_expr_scope, + .kind = kind, + }); + errdefer module.a().destroy(self); + + defer_expr_scope.base.ref(); + + if (parent) |p| p.ref(); + return self; + } + + pub fn destroy(self: *Defer, module: *Module) void { + self.defer_expr_scope.base.deref(module); + module.a().destroy(self); + } + }; + + pub const DeferExpr = struct { + base: Scope, + expr_node: *ast.Node, + + /// Creates a DeferExpr scope with 1 reference + pub fn create(module: *Module, parent: ?*Scope, expr_node: *ast.Node) !*DeferExpr { + const self = try module.a().create(DeferExpr{ + .base = Scope{ + .id = Id.DeferExpr, + .parent = parent, + .ref_count = 1, + }, + .expr_node = expr_node, + }); + errdefer module.a().destroy(self); + + if (parent) |p| p.ref(); + return self; + } + + pub fn destroy(self: *DeferExpr, module: *Module) void { + module.a().destroy(self); + } }; }; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig new file mode 100644 index 0000000000..4b3918854d --- /dev/null +++ b/src-self-hosted/type.zig @@ -0,0 +1,268 @@ +const builtin = @import("builtin"); +const Scope = @import("scope.zig").Scope; +const Module = @import("module.zig").Module; +const Value = @import("value.zig").Value; + +pub const Type = struct { + base: Value, + id: Id, + + pub const Id = builtin.TypeId; + + pub fn destroy(base: *Type, module: *Module) void { + switch (base.id) { + Id.Struct => @fieldParentPtr(Struct, "base", base).destroy(module), + Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(module), + Id.Type => @fieldParentPtr(MetaType, "base", base).destroy(module), + Id.Void => @fieldParentPtr(Void, "base", base).destroy(module), + Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(module), + Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(module), + Id.Int => @fieldParentPtr(Int, "base", base).destroy(module), + Id.Float => @fieldParentPtr(Float, "base", base).destroy(module), + Id.Pointer => @fieldParentPtr(Pointer, "base", base).destroy(module), + Id.Array => @fieldParentPtr(Array, "base", base).destroy(module), + Id.ComptimeFloat => @fieldParentPtr(ComptimeFloat, "base", base).destroy(module), + Id.ComptimeInt => @fieldParentPtr(ComptimeInt, "base", base).destroy(module), + Id.Undefined => @fieldParentPtr(Undefined, "base", base).destroy(module), + Id.Null => @fieldParentPtr(Null, "base", base).destroy(module), + Id.Optional => @fieldParentPtr(Optional, "base", base).destroy(module), + Id.ErrorUnion => @fieldParentPtr(ErrorUnion, "base", base).destroy(module), + Id.ErrorSet => @fieldParentPtr(ErrorSet, "base", base).destroy(module), + Id.Enum => @fieldParentPtr(Enum, "base", base).destroy(module), + Id.Union => @fieldParentPtr(Union, "base", base).destroy(module), + Id.Namespace => @fieldParentPtr(Namespace, "base", base).destroy(module), + Id.Block => @fieldParentPtr(Block, "base", base).destroy(module), + Id.BoundFn => @fieldParentPtr(BoundFn, "base", base).destroy(module), + Id.ArgTuple => @fieldParentPtr(ArgTuple, "base", base).destroy(module), + Id.Opaque => @fieldParentPtr(Opaque, "base", base).destroy(module), + Id.Promise => @fieldParentPtr(Promise, "base", base).destroy(module), + } + } + + pub const Struct = struct { + base: Type, + decls: *Scope.Decls, + + pub fn destroy(self: *Struct, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Fn = struct { + base: Type, + + pub fn create(module: *Module) !*Fn { + return module.a().create(Fn{ + .base = Type{ + .base = Value{ + .id = Value.Id.Type, + .typeof = &MetaType.get(module).base, + .ref_count = 1, + }, + .id = builtin.TypeId.Fn, + }, + }); + } + + pub fn destroy(self: *Fn, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const MetaType = struct { + base: Type, + value: *Type, + + /// Adds 1 reference to the resulting type + pub fn get(module: *Module) *MetaType { + module.meta_type.base.base.ref(); + return module.meta_type; + } + + pub fn destroy(self: *MetaType, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Void = struct { + base: Type, + + /// Adds 1 reference to the resulting type + pub fn get(module: *Module) *Void { + module.void_type.base.base.ref(); + return module.void_type; + } + + pub fn destroy(self: *Void, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Bool = struct { + base: Type, + + /// Adds 1 reference to the resulting type + pub fn get(module: *Module) *Bool { + module.bool_type.base.base.ref(); + return module.bool_type; + } + + pub fn destroy(self: *Bool, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const NoReturn = struct { + base: Type, + + /// Adds 1 reference to the resulting type + pub fn get(module: *Module) *NoReturn { + module.noreturn_type.base.base.ref(); + return module.noreturn_type; + } + + pub fn destroy(self: *NoReturn, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Int = struct { + base: Type, + + pub fn destroy(self: *Int, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Float = struct { + base: Type, + + pub fn destroy(self: *Float, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Pointer = struct { + base: Type, + + pub fn destroy(self: *Pointer, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Array = struct { + base: Type, + + pub fn destroy(self: *Array, module: *Module) void { + module.a().destroy(self); + } + }; + pub const ComptimeFloat = struct { + base: Type, + + pub fn destroy(self: *ComptimeFloat, module: *Module) void { + module.a().destroy(self); + } + }; + pub const ComptimeInt = struct { + base: Type, + + pub fn destroy(self: *ComptimeInt, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Undefined = struct { + base: Type, + + pub fn destroy(self: *Undefined, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Null = struct { + base: Type, + + pub fn destroy(self: *Null, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Optional = struct { + base: Type, + + pub fn destroy(self: *Optional, module: *Module) void { + module.a().destroy(self); + } + }; + pub const ErrorUnion = struct { + base: Type, + + pub fn destroy(self: *ErrorUnion, module: *Module) void { + module.a().destroy(self); + } + }; + pub const ErrorSet = struct { + base: Type, + + pub fn destroy(self: *ErrorSet, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Enum = struct { + base: Type, + + pub fn destroy(self: *Enum, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Union = struct { + base: Type, + + pub fn destroy(self: *Union, module: *Module) void { + module.a().destroy(self); + } + }; + pub const Namespace = struct { + base: Type, + + pub fn destroy(self: *Namespace, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Block = struct { + base: Type, + + pub fn destroy(self: *Block, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const BoundFn = struct { + base: Type, + + pub fn destroy(self: *BoundFn, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const ArgTuple = struct { + base: Type, + + pub fn destroy(self: *ArgTuple, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Opaque = struct { + base: Type, + + pub fn destroy(self: *Opaque, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Promise = struct { + base: Type, + + pub fn destroy(self: *Promise, module: *Module) void { + module.a().destroy(self); + } + }; +}; diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig new file mode 100644 index 0000000000..b53d03d0ad --- /dev/null +++ b/src-self-hosted/value.zig @@ -0,0 +1,125 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Scope = @import("scope.zig").Scope; +const Module = @import("module.zig").Module; + +/// Values are ref-counted, heap-allocated, and copy-on-write +/// If there is only 1 ref then write need not copy +pub const Value = struct { + id: Id, + typeof: *Type, + ref_count: usize, + + pub fn ref(base: *Value) void { + base.ref_count += 1; + } + + pub fn deref(base: *Value, module: *Module) void { + base.ref_count -= 1; + if (base.ref_count == 0) { + base.typeof.base.deref(module); + switch (base.id) { + Id.Type => @fieldParentPtr(Type, "base", base).destroy(module), + Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(module), + Id.Void => @fieldParentPtr(Void, "base", base).destroy(module), + Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(module), + Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(module), + } + } + } + + pub fn dump(base: *const Value) void { + std.debug.warn("{}", @tagName(base.id)); + } + + pub const Id = enum { + Type, + Fn, + Void, + Bool, + NoReturn, + }; + + pub const Type = @import("type.zig").Type; + + pub const Fn = struct { + base: Value, + + /// parent should be the top level decls or container decls + fndef_scope: *Scope.FnDef, + + /// parent is scope for last parameter + child_scope: *Scope, + + /// parent is child_scope + block_scope: *Scope.Block, + + /// Creates a Fn value with 1 ref + pub fn create(module: *Module, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef) !*Fn { + const self = try module.a().create(Fn{ + .base = Value{ + .id = Value.Id.Fn, + .typeof = &fn_type.base, + .ref_count = 1, + }, + .fndef_scope = fndef_scope, + .child_scope = &fndef_scope.base, + .block_scope = undefined, + }); + fn_type.base.base.ref(); + fndef_scope.fn_val = self; + fndef_scope.base.ref(); + return self; + } + + pub fn destroy(self: *Fn, module: *Module) void { + self.fndef_scope.base.deref(module); + module.a().destroy(self); + } + }; + + pub const Void = struct { + base: Value, + + pub fn get(module: *Module) *Void { + module.void_value.base.ref(); + return module.void_value; + } + + pub fn destroy(self: *Void, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const Bool = struct { + base: Value, + x: bool, + + pub fn get(module: *Module, x: bool) *Bool { + if (x) { + module.true_value.base.ref(); + return module.true_value; + } else { + module.false_value.base.ref(); + return module.false_value; + } + } + + pub fn destroy(self: *Bool, module: *Module) void { + module.a().destroy(self); + } + }; + + pub const NoReturn = struct { + base: Value, + + pub fn get(module: *Module) *NoReturn { + module.noreturn_value.base.ref(); + return module.noreturn_value; + } + + pub fn destroy(self: *NoReturn, module: *Module) void { + module.a().destroy(self); + } + }; +}; diff --git a/src-self-hosted/visib.zig b/src-self-hosted/visib.zig new file mode 100644 index 0000000000..3704600cca --- /dev/null +++ b/src-self-hosted/visib.zig @@ -0,0 +1,4 @@ +pub const Visib = enum { + Private, + Pub, +}; diff --git a/std/event/future.zig b/std/event/future.zig index b6ec861f77..23fa570c8f 100644 --- a/std/event/future.zig +++ b/std/event/future.zig @@ -57,7 +57,7 @@ test "std.event.Future" { const allocator = &da.allocator; var loop: Loop = undefined; - try loop.initSingleThreaded(allocator); + try loop.initMultiThreaded(allocator); defer loop.deinit(); const handle = try async testFuture(&loop); diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 63518c5182..004f9278b9 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -970,14 +970,8 @@ pub const Node = struct { pub const Defer = struct { base: Node, defer_token: TokenIndex, - kind: Kind, expr: *Node, - const Kind = enum { - Error, - Unconditional, - }; - pub fn iterate(self: *Defer, index: usize) ?*Node { var i = index; diff --git a/std/zig/parse.zig b/std/zig/parse.zig index 9f0371d4da..9842ba2a17 100644 --- a/std/zig/parse.zig +++ b/std/zig/parse.zig @@ -1041,11 +1041,6 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { const node = try arena.create(ast.Node.Defer{ .base = ast.Node{ .id = ast.Node.Id.Defer }, .defer_token = token_index, - .kind = switch (token_ptr.id) { - Token.Id.Keyword_defer => ast.Node.Defer.Kind.Unconditional, - Token.Id.Keyword_errdefer => ast.Node.Defer.Kind.Error, - else => unreachable, - }, .expr = undefined, }); const node_ptr = try block.statements.addOne();