diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index ce3d9080b..6f2db321e 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -1,2590 +1,250 @@ const std = @import("std"); -const Compilation = @import("compilation.zig").Compilation; -const Scope = @import("scope.zig").Scope; -const ast = std.zig.ast; +const mem = std.mem; 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 Span = @import("errmsg.zig").Span; -const llvm = @import("llvm.zig"); -const codegen = @import("codegen.zig"); -const ObjectFile = codegen.ObjectFile; -const Decl = @import("decl.zig").Decl; -const mem = std.mem; - -pub const LVal = enum { - None, - Ptr, -}; - -pub const IrVal = union(enum) { - Unknown, - KnownType: *Type, - KnownValue: *Value, - - const Init = enum { - Unknown, - NoReturn, - Void, - }; - - pub fn dump(self: IrVal) void { - switch (self) { - .Unknown => std.debug.warn("Unknown", .{}), - .KnownType => |typ| { - std.debug.warn("KnownType(", .{}); - typ.dump(); - std.debug.warn(")", .{}); - }, - .KnownValue => |value| { - std.debug.warn("KnownValue(", .{}); - value.dump(); - std.debug.warn(")", .{}); - }, - } - } -}; pub const Inst = struct { - id: Id, - scope: *Scope, - debug_id: usize, - val: IrVal, - ref_count: usize, - span: Span, - owner_bb: *BasicBlock, + tag: Tag, - /// true if this instruction was generated by zig and not from user code - is_generated: bool, - - /// the instruction that is derived from this one in analysis - child: ?*Inst, - - /// the instruction that this one derives from in analysis - parent: ?*Inst, - - /// populated durign codegen - llvm_value: ?*llvm.Value, - - pub fn cast(base: *Inst, comptime T: type) ?*T { - if (base.id == comptime typeToId(T)) { - return @fieldParentPtr(T, "base", base); - } - return null; - } - - pub fn typeToId(comptime T: type) Id { - inline for (@typeInfo(Id).Enum.fields) |f| { - if (T == @field(Inst, f.name)) { - return @field(Id, f.name); - } - } - unreachable; - } - - pub fn dump(base: *const Inst) void { - inline for (@typeInfo(Id).Enum.fields) |f| { - if (base.id == @field(Id, f.name)) { - const T = @field(Inst, f.name); - std.debug.warn("#{} = {}(", .{ base.debug_id, @tagName(base.id) }); - @fieldParentPtr(T, "base", base).dump(); - std.debug.warn(")", .{}); - return; - } - } - unreachable; - } - - pub fn hasSideEffects(base: *const Inst) bool { - inline for (@typeInfo(Id).Enum.fields) |f| { - if (base.id == @field(Id, f.name)) { - const T = @field(Inst, f.name); - return @fieldParentPtr(T, "base", base).hasSideEffects(); - } - } - unreachable; - } - - pub fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst { - switch (base.id) { - .Return => return @fieldParentPtr(Return, "base", base).analyze(ira), - .Const => return @fieldParentPtr(Const, "base", base).analyze(ira), - .Call => return @fieldParentPtr(Call, "base", base).analyze(ira), - .DeclRef => return @fieldParentPtr(DeclRef, "base", base).analyze(ira), - .Ref => return @fieldParentPtr(Ref, "base", base).analyze(ira), - .DeclVar => return @fieldParentPtr(DeclVar, "base", base).analyze(ira), - .CheckVoidStmt => return @fieldParentPtr(CheckVoidStmt, "base", base).analyze(ira), - .Phi => return @fieldParentPtr(Phi, "base", base).analyze(ira), - .Br => return @fieldParentPtr(Br, "base", base).analyze(ira), - .AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira), - .PtrType => return @fieldParentPtr(PtrType, "base", base).analyze(ira), - .VarPtr => return @fieldParentPtr(VarPtr, "base", base).analyze(ira), - .LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).analyze(ira), - } - } - - pub fn render(base: *Inst, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?*llvm.Value) { - switch (base.id) { - .Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val), - .Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val), - .Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val), - .VarPtr => return @fieldParentPtr(VarPtr, "base", base).render(ofile, fn_val), - .LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).render(ofile, fn_val), - .DeclRef => unreachable, - .PtrType => unreachable, - .Ref => @panic("TODO"), - .DeclVar => @panic("TODO"), - .CheckVoidStmt => @panic("TODO"), - .Phi => @panic("TODO"), - .Br => @panic("TODO"), - .AddImplicitReturnType => @panic("TODO"), - } - } - - fn ref(base: *Inst, builder: *Builder) void { - base.ref_count += 1; - if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) { - base.owner_bb.ref(builder); - } - } - - fn copyVal(base: *Inst, comp: *Compilation) !*Value { - if (base.parent.?.ref_count == 0) { - return base.val.KnownValue.derefAndCopy(comp); - } - return base.val.KnownValue.copy(comp); - } - - fn getAsParam(param: *Inst) !*Inst { - param.ref_count -= 1; - const child = param.child orelse return error.SemanticAnalysisFailed; - switch (child.val) { - .Unknown => return error.SemanticAnalysisFailed, - else => return child, - } - } - - fn getConstVal(self: *Inst, ira: *Analyze) !*Value { - if (self.isCompTime()) { - return self.val.KnownValue; - } else { - try ira.addCompileError(self.span, "unable to evaluate constant expression", .{}); - return error.SemanticAnalysisFailed; - } - } - - fn getAsConstType(param: *Inst, ira: *Analyze) !*Type { - const meta_type = Type.MetaType.get(ira.irb.comp); - meta_type.base.base.deref(ira.irb.comp); - - const inst = try param.getAsParam(); - const casted = try ira.implicitCast(inst, &meta_type.base); - const val = try casted.getConstVal(ira); - return val.cast(Value.Type).?; - } - - fn getAsConstAlign(param: *Inst, ira: *Analyze) !u32 { - return error.Unimplemented; - //const align_type = Type.Int.get_align(ira.irb.comp); - //align_type.base.base.deref(ira.irb.comp); - - //const inst = try param.getAsParam(); - //const casted = try ira.implicitCast(inst, align_type); - //const val = try casted.getConstVal(ira); - - //uint32_t align_bytes = bigint_as_unsigned(&const_val->data.x_bigint); - //if (align_bytes == 0) { - // ir_add_error(ira, value, buf_sprintf("alignment must be >= 1")); - // return false; - //} - - //if (!is_power_of_2(align_bytes)) { - // ir_add_error(ira, value, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes)); - // return false; - //} - } - - /// asserts that the type is known - fn getKnownType(self: *Inst) *Type { - switch (self.val) { - .KnownType => |typ| return typ, - .KnownValue => |value| return value.typ, - .Unknown => unreachable, - } - } - - pub fn setGenerated(base: *Inst) void { - base.is_generated = true; - } - - pub fn isNoReturn(base: *const Inst) bool { - switch (base.val) { - .Unknown => return false, - .KnownValue => |x| return x.typ.id == .NoReturn, - .KnownType => |typ| return typ.id == .NoReturn, - } - } - - pub fn isCompTime(base: *const Inst) bool { - return base.val == .KnownValue; - } - - pub fn linkToParent(self: *Inst, parent: *Inst) void { - assert(self.parent == null); - assert(parent.child == null); - self.parent = parent; - parent.child = self; - } - - pub const Id = enum { - Return, - Const, - Ref, - DeclVar, - CheckVoidStmt, - Phi, - Br, - AddImplicitReturnType, - Call, - DeclRef, - PtrType, - VarPtr, - LoadPtr, + pub const all_types = .{ + Constant, + PtrToInt, + FieldPtr, + Deref, + Assembly, + Unreach, }; - pub const Call = struct { - base: Inst, - params: Params, - - const Params = struct { - fn_ref: *Inst, - args: []*Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(self: *const Call) void { - std.debug.warn("#{}(", .{self.params.fn_ref.debug_id}); - for (self.params.args) |arg| { - std.debug.warn("#{},", .{arg.debug_id}); - } - std.debug.warn(")", .{}); - } - - pub fn hasSideEffects(self: *const Call) bool { - return true; - } - - pub fn analyze(self: *const Call, ira: *Analyze) !*Inst { - const fn_ref = try self.params.fn_ref.getAsParam(); - const fn_ref_type = fn_ref.getKnownType(); - const fn_type = fn_ref_type.cast(Type.Fn) orelse { - try ira.addCompileError(fn_ref.span, "type '{}' not a function", .{fn_ref_type.name}); - return error.SemanticAnalysisFailed; - }; - - const fn_type_param_count = fn_type.paramCount(); - - if (fn_type_param_count != self.params.args.len) { - try ira.addCompileError(self.base.span, "expected {} arguments, found {}", .{ - fn_type_param_count, - self.params.args.len, - }); - return error.SemanticAnalysisFailed; - } - - const args = try ira.irb.arena().alloc(*Inst, self.params.args.len); - for (self.params.args) |arg, i| { - args[i] = try arg.getAsParam(); - } - const new_inst = try ira.irb.build(Call, self.base.scope, self.base.span, Params{ - .fn_ref = fn_ref, - .args = args, - }); - new_inst.val = IrVal{ .KnownType = fn_type.key.data.Normal.return_type }; - return new_inst; - } - - pub fn render(self: *Call, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value { - const fn_ref = self.params.fn_ref.llvm_value.?; - - const args = try ofile.arena.alloc(*llvm.Value, self.params.args.len); - for (self.params.args) |arg, i| { - args[i] = arg.llvm_value.?; - } - - const llvm_cc = llvm.CCallConv; - const call_attr = llvm.CallAttr.Auto; - - return llvm.BuildCall( - ofile.builder, - fn_ref, - args.ptr, - @intCast(c_uint, args.len), - llvm_cc, - call_attr, - "", - ) orelse error.OutOfMemory; - } + pub const Tag = enum { + constant, + ptrtoint, + fieldptr, + deref, + @"asm", + unreach, }; - pub const Const = struct { - base: Inst, - params: Params, - - const Params = struct {}; - - // Use Builder.buildConst* methods, or, after building a Const instruction, - // manually set the ir_val field. - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(self: *const Const) void { - self.base.val.KnownValue.dump(); - } - - pub fn hasSideEffects(self: *const Const) bool { - return false; - } - - pub fn analyze(self: *const Const, ira: *Analyze) !*Inst { - const new_inst = try ira.irb.build(Const, self.base.scope, self.base.span, Params{}); - new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() }; - return new_inst; - } - - pub fn render(self: *Const, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value { - return self.base.val.KnownValue.getLlvmConst(ofile); - } - }; - - pub const Return = struct { - base: Inst, - params: Params, - - const Params = struct { - return_value: *Inst, - }; - - const ir_val_init = IrVal.Init.NoReturn; - - pub fn dump(self: *const Return) void { - std.debug.warn("#{}", .{self.params.return_value.debug_id}); - } - - pub fn hasSideEffects(self: *const Return) bool { - return true; - } - - pub fn analyze(self: *const Return, ira: *Analyze) !*Inst { - const value = try self.params.return_value.getAsParam(); - const casted_value = try ira.implicitCast(value, ira.explicit_return_type); - - // TODO detect returning local variable address - - return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value }); - } - - pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value { - const value = self.params.return_value.llvm_value; - const return_type = self.params.return_value.getKnownType(); - - if (return_type.handleIsPtr()) { - @panic("TODO"); - } else { - _ = llvm.BuildRet(ofile.builder, value) orelse return error.OutOfMemory; - } - return null; - } - }; - - pub const Ref = struct { - base: Inst, - params: Params, - - const Params = struct { - target: *Inst, - mut: Type.Pointer.Mut, - volatility: Type.Pointer.Vol, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const Ref) void {} - - pub fn hasSideEffects(inst: *const Ref) bool { - return false; - } - - pub fn analyze(self: *const Ref, ira: *Analyze) !*Inst { - const target = try self.params.target.getAsParam(); - - if (ira.getCompTimeValOrNullUndefOk(target)) |val| { - return ira.getCompTimeRef( - val, - Value.Ptr.Mut.CompTimeConst, - self.params.mut, - self.params.volatility, - ); - } - - const new_inst = try ira.irb.build(Ref, self.base.scope, self.base.span, Params{ - .target = target, - .mut = self.params.mut, - .volatility = self.params.volatility, - }); - const elem_type = target.getKnownType(); - const ptr_type = try Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ - .child_type = elem_type, - .mut = self.params.mut, - .vol = self.params.volatility, - .size = .One, - .alignment = .Abi, - }); - // TODO: potentially set the hint that this is a stack pointer. But it might not be - this - // could be a ref of a global, for example - new_inst.val = IrVal{ .KnownType = &ptr_type.base }; - // TODO potentially add an alloca entry here - return new_inst; - } - }; - - pub const DeclRef = struct { - base: Inst, - params: Params, - - const Params = struct { - decl: *Decl, - lval: LVal, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const DeclRef) void {} - - pub fn hasSideEffects(inst: *const DeclRef) bool { - return false; - } - - pub fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst { - (ira.irb.comp.resolveDecl(self.params.decl)) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => return error.SemanticAnalysisFailed, - }; - switch (self.params.decl.id) { - .CompTime => unreachable, - .Var => return error.Unimplemented, - .Fn => { - const fn_decl = @fieldParentPtr(Decl.Fn, "base", self.params.decl); - const decl_val = switch (fn_decl.value) { - .Unresolved => unreachable, - .Fn => |fn_val| &fn_val.base, - .FnProto => |fn_proto| &fn_proto.base, - }; - switch (self.params.lval) { - .None => { - return ira.irb.buildConstValue(self.base.scope, self.base.span, decl_val); - }, - .Ptr => return error.Unimplemented, - } - }, - } - } - }; - - pub const VarPtr = struct { - base: Inst, - params: Params, - - const Params = struct { - var_scope: *Scope.Var, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const VarPtr) void { - std.debug.warn("{}", .{inst.params.var_scope.name}); - } - - pub fn hasSideEffects(inst: *const VarPtr) bool { - return false; - } - - pub fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst { - switch (self.params.var_scope.data) { - .Const => @panic("TODO"), - .Param => |param| { - const new_inst = try ira.irb.build( - Inst.VarPtr, - self.base.scope, - self.base.span, - Inst.VarPtr.Params{ .var_scope = self.params.var_scope }, - ); - const ptr_type = try Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ - .child_type = param.typ, - .mut = .Const, - .vol = .Non, - .size = .One, - .alignment = .Abi, - }); - new_inst.val = IrVal{ .KnownType = &ptr_type.base }; - return new_inst; - }, - } - } - - pub fn render(self: *VarPtr, ofile: *ObjectFile, fn_val: *Value.Fn) *llvm.Value { - switch (self.params.var_scope.data) { - .Const => unreachable, // turned into Inst.Const in analyze pass - .Param => |param| return param.llvm_value, - } - } - }; - - pub const LoadPtr = struct { - base: Inst, - params: Params, - - const Params = struct { - target: *Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const LoadPtr) void {} - - pub fn hasSideEffects(inst: *const LoadPtr) bool { - return false; - } - - pub fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst { - const target = try self.params.target.getAsParam(); - const target_type = target.getKnownType(); - if (target_type.id != .Pointer) { - try ira.addCompileError(self.base.span, "dereference of non pointer type '{}'", .{target_type.name}); - return error.SemanticAnalysisFailed; - } - const ptr_type = @fieldParentPtr(Type.Pointer, "base", target_type); - // if (instr_is_comptime(ptr)) { - // if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst || - // ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar) - // { - // ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &ptr->value); - // if (pointee->special != ConstValSpecialRuntime) { - // IrInstruction *result = ir_create_const(&ira->new_irb, source_instruction->scope, - // source_instruction->source_node, child_type); - // copy_const_val(&result->value, pointee, ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst); - // result->value.type = child_type; - // return result; - // } - // } - // } - const new_inst = try ira.irb.build( - Inst.LoadPtr, - self.base.scope, - self.base.span, - Inst.LoadPtr.Params{ .target = target }, - ); - new_inst.val = IrVal{ .KnownType = ptr_type.key.child_type }; - return new_inst; - } - - pub fn render(self: *LoadPtr, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value { - const child_type = self.base.getKnownType(); - if (!child_type.hasBits()) { - return null; - } - const ptr = self.params.target.llvm_value.?; - const ptr_type = self.params.target.getKnownType().cast(Type.Pointer).?; - - return try codegen.getHandleValue(ofile, ptr, ptr_type); - - //uint32_t unaligned_bit_count = ptr_type->data.pointer.unaligned_bit_count; - //if (unaligned_bit_count == 0) - // return get_handle_value(g, ptr, child_type, ptr_type); - - //bool big_endian = g->is_big_endian; - - //assert(!handle_is_ptr(child_type)); - //LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, ""); - - //uint32_t bit_offset = ptr_type->data.pointer.bit_offset; - //uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int)); - //uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset; - - //LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false); - //LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, ""); - - //return LLVMBuildTrunc(g->builder, shifted_value, child_type->type_ref, ""); - } - }; - - pub const PtrType = struct { - base: Inst, - params: Params, - - const Params = struct { - child_type: *Inst, - mut: Type.Pointer.Mut, - vol: Type.Pointer.Vol, - size: Type.Pointer.Size, - alignment: ?*Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const PtrType) void {} - - pub fn hasSideEffects(inst: *const PtrType) bool { - return false; - } - - pub fn analyze(self: *const PtrType, ira: *Analyze) !*Inst { - const child_type = try self.params.child_type.getAsConstType(ira); - // if (child_type->id == TypeTableEntryIdUnreachable) { - // ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed")); - // return ira->codegen->builtin_types.entry_invalid; - // } else if (child_type->id == TypeTableEntryIdOpaque && instruction->ptr_len == PtrLenUnknown) { - // ir_add_error(ira, &instruction->base, buf_sprintf("unknown-length pointer to opaque")); - // return ira->codegen->builtin_types.entry_invalid; - // } - const alignment = if (self.params.alignment) |align_inst| blk: { - const amt = try align_inst.getAsConstAlign(ira); - break :blk Type.Pointer.Align{ .Override = amt }; - } else blk: { - break :blk .Abi; - }; - const ptr_type = try Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{ - .child_type = child_type, - .mut = self.params.mut, - .vol = self.params.vol, - .size = self.params.size, - .alignment = alignment, - }); - ptr_type.base.base.deref(ira.irb.comp); - - return ira.irb.buildConstValue(self.base.scope, self.base.span, &ptr_type.base.base); - } - }; - - pub const DeclVar = struct { - base: Inst, - params: Params, - - const Params = struct { - variable: *Variable, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const DeclVar) void {} - - pub fn hasSideEffects(inst: *const DeclVar) bool { - return true; - } - - pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Inst { - return error.Unimplemented; // TODO - } - }; - - pub const CheckVoidStmt = struct { - base: Inst, - params: Params, - - const Params = struct { - target: *Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(self: *const CheckVoidStmt) void { - std.debug.warn("#{}", .{self.params.target.debug_id}); - } - - pub fn hasSideEffects(inst: *const CheckVoidStmt) bool { - return true; - } - - pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst { - const target = try self.params.target.getAsParam(); - if (target.getKnownType().id != .Void) { - try ira.addCompileError(self.base.span, "expression value is ignored", .{}); - return error.SemanticAnalysisFailed; - } - return ira.irb.buildConstVoid(self.base.scope, self.base.span, true); - } - }; - - pub const Phi = struct { - base: Inst, - params: Params, - - const Params = struct { - incoming_blocks: []*BasicBlock, - incoming_values: []*Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const Phi) void {} - - pub fn hasSideEffects(inst: *const Phi) bool { - return false; - } - - pub fn analyze(self: *const Phi, ira: *Analyze) !*Inst { - return error.Unimplemented; // TODO - } - }; - - pub const Br = struct { - base: Inst, - params: Params, - - const Params = struct { - dest_block: *BasicBlock, - is_comptime: *Inst, - }; - - const ir_val_init = IrVal.Init.NoReturn; - - pub fn dump(inst: *const Br) void {} - - pub fn hasSideEffects(inst: *const Br) bool { - return true; - } - - pub fn analyze(self: *const Br, ira: *Analyze) !*Inst { - return error.Unimplemented; // TODO - } - }; - - pub const CondBr = struct { - base: Inst, - params: Params, - - const Params = struct { - condition: *Inst, - then_block: *BasicBlock, - else_block: *BasicBlock, - is_comptime: *Inst, - }; - - const ir_val_init = IrVal.Init.NoReturn; - - pub fn dump(inst: *const CondBr) void {} - - pub fn hasSideEffects(inst: *const CondBr) bool { - return true; - } - - pub fn analyze(self: *const CondBr, ira: *Analyze) !*Inst { - return error.Unimplemented; // TODO - } - }; - - pub const AddImplicitReturnType = struct { - base: Inst, - params: Params, - - pub const Params = struct { - target: *Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const AddImplicitReturnType) void { - std.debug.warn("#{}", .{inst.params.target.debug_id}); - } - - pub fn hasSideEffects(inst: *const AddImplicitReturnType) bool { - return true; - } - - pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Inst { - const target = try self.params.target.getAsParam(); - try ira.src_implicit_return_type_list.append(target); - return ira.irb.buildConstVoid(self.base.scope, self.base.span, true); - } - }; - - pub const TestErr = struct { - base: Inst, - params: Params, - - pub const Params = struct { - target: *Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const TestErr) void { - std.debug.warn("#{}", .{inst.params.target.debug_id}); - } - - pub fn hasSideEffects(inst: *const TestErr) bool { - return false; - } - - pub fn analyze(self: *const TestErr, ira: *Analyze) !*Inst { - const target = try self.params.target.getAsParam(); - const target_type = target.getKnownType(); - switch (target_type.id) { - .ErrorUnion => { - return error.Unimplemented; - // if (instr_is_comptime(value)) { - // ConstExprValue *err_union_val = ir_resolve_const(ira, value, UndefBad); - // if (!err_union_val) - // return ira->codegen->builtin_types.entry_invalid; - - // if (err_union_val->special != ConstValSpecialRuntime) { - // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); - // out_val->data.x_bool = (err_union_val->data.x_err_union.err != nullptr); - // return ira->codegen->builtin_types.entry_bool; - // } - // } - - // TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type; - // if (!resolve_inferred_error_set(ira->codegen, err_set_type, instruction->base.source_node)) { - // return ira->codegen->builtin_types.entry_invalid; - // } - // if (!type_is_global_error_set(err_set_type) && - // err_set_type->data.error_set.err_count == 0) - // { - // assert(err_set_type->data.error_set.infer_fn == nullptr); - // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); - // out_val->data.x_bool = false; - // return ira->codegen->builtin_types.entry_bool; - // } - - // ir_build_test_err_from(&ira->new_irb, &instruction->base, value); - // return ira->codegen->builtin_types.entry_bool; - }, - .ErrorSet => { - return ira.irb.buildConstBool(self.base.scope, self.base.span, true); - }, - else => { - return ira.irb.buildConstBool(self.base.scope, self.base.span, false); - }, - } - } - }; - - pub const TestCompTime = struct { - base: Inst, - params: Params, - - pub const Params = struct { - target: *Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const TestCompTime) void { - std.debug.warn("#{}", .{inst.params.target.debug_id}); - } - - pub fn hasSideEffects(inst: *const TestCompTime) bool { - return false; - } - - pub fn analyze(self: *const TestCompTime, ira: *Analyze) !*Inst { - const target = try self.params.target.getAsParam(); - return ira.irb.buildConstBool(self.base.scope, self.base.span, target.isCompTime()); - } - }; - - pub const SaveErrRetAddr = struct { - base: Inst, - params: Params, - - const Params = struct {}; - - const ir_val_init = IrVal.Init.Unknown; - - pub fn dump(inst: *const SaveErrRetAddr) void {} - - pub fn hasSideEffects(inst: *const SaveErrRetAddr) bool { - return true; - } - - pub fn analyze(self: *const SaveErrRetAddr, ira: *Analyze) !*Inst { - return ira.irb.build(Inst.SaveErrRetAddr, self.base.scope, self.base.span, Params{}); - } - }; -}; - -pub const Variable = struct { - child_scope: *Scope, -}; - -pub const BasicBlock = struct { - ref_count: usize, - name_hint: [*:0]const u8, - debug_id: usize, - scope: *Scope, - instruction_list: std.ArrayList(*Inst), - ref_instruction: ?*Inst, - - /// for codegen - llvm_block: *llvm.BasicBlock, - llvm_exit_block: *llvm.BasicBlock, - - /// the basic block that is derived from this one in analysis - child: ?*BasicBlock, - - /// the basic block that this one derives from in analysis - parent: ?*BasicBlock, - - pub fn ref(self: *BasicBlock, builder: *Builder) void { - self.ref_count += 1; - } - - pub fn linkToParent(self: *BasicBlock, parent: *BasicBlock) void { - assert(self.parent == null); - assert(parent.child == null); - self.parent = parent; - parent.child = self; - } -}; - -/// Stuff that survives longer than Builder -pub const Code = struct { - basic_block_list: std.ArrayList(*BasicBlock), - arena: std.heap.ArenaAllocator, - return_type: ?*Type, - tree_scope: *Scope.AstTree, - - /// allocator is comp.gpa() - 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.span()) |bb| { - std.debug.warn("{s}_{}:\n", .{ bb.name_hint, bb.debug_id }); - for (bb.instruction_list.span()) |instr| { - std.debug.warn(" ", .{}); - instr.dump(); - std.debug.warn("\n", .{}); - } - } - } - - /// returns a ref-incremented value, or adds a compile error - pub fn getCompTimeResult(self: *Code, comp: *Compilation) !*Value { - const bb = self.basic_block_list.at(0); - for (bb.instruction_list.span()) |inst| { - if (inst.cast(Inst.Return)) |ret_inst| { - const ret_value = ret_inst.params.return_value; - if (ret_value.isCompTime()) { - return ret_value.val.KnownValue.getRef(); - } - try comp.addCompileError( - self.tree_scope, - ret_value.span, - "unable to evaluate constant expression", - .{}, - ); - return error.SemanticAnalysisFailed; - } else if (inst.hasSideEffects()) { - try comp.addCompileError( - self.tree_scope, - inst.span, - "unable to evaluate constant expression", - .{}, - ); - return error.SemanticAnalysisFailed; - } - } - unreachable; - } -}; - -pub const Builder = struct { - comp: *Compilation, - code: *Code, - current_basic_block: *BasicBlock, - next_debug_id: usize, - is_comptime: bool, - is_async: bool, - begin_scope: ?*Scope, - - pub const Error = Analyze.Error; - - pub fn init(comp: *Compilation, tree_scope: *Scope.AstTree, begin_scope: ?*Scope) !Builder { - const code = try comp.gpa().create(Code); - code.* = Code{ - .basic_block_list = undefined, - .arena = std.heap.ArenaAllocator.init(comp.gpa()), - .return_type = null, - .tree_scope = tree_scope, - }; - code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator); - errdefer code.destroy(comp.gpa()); - - return Builder{ - .comp = comp, - .current_basic_block = undefined, - .code = code, - .next_debug_id = 0, - .is_comptime = false, - .is_async = false, - .begin_scope = begin_scope, - }; - } - - pub fn abort(self: *Builder) void { - self.code.destroy(self.comp.gpa()); - } - - /// 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: [*:0]const u8) !*BasicBlock { - const basic_block = try self.arena().create(BasicBlock); - basic_block.* = BasicBlock{ - .ref_count = 0, - .name_hint = name_hint, - .debug_id = self.next_debug_id, - .scope = scope, - .instruction_list = std.ArrayList(*Inst).init(self.arena()), - .child = null, - .parent = null, - .ref_instruction = null, - .llvm_block = undefined, - .llvm_exit_block = undefined, - }; - 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 genNodeRecursive(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst { - const alloc = irb.comp.gpa(); - var frame = try alloc.create(@Frame(genNode)); - defer alloc.destroy(frame); - frame.* = async irb.genNode(node, scope, lval); - return await frame; - } - - pub async fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst { - switch (node.id) { - .Root => unreachable, - .Use => unreachable, - .TestDecl => unreachable, - .VarDecl => return error.Unimplemented, - .Defer => return error.Unimplemented, - .InfixOp => return error.Unimplemented, - .PrefixOp => { - const prefix_op = @fieldParentPtr(ast.Node.PrefixOp, "base", node); - switch (prefix_op.op) { - .AddressOf => return error.Unimplemented, - .ArrayType => |n| return error.Unimplemented, - .Await => return error.Unimplemented, - .BitNot => return error.Unimplemented, - .BoolNot => return error.Unimplemented, - .OptionalType => return error.Unimplemented, - .Negation => return error.Unimplemented, - .NegationWrap => return error.Unimplemented, - .Resume => return error.Unimplemented, - .PtrType => |ptr_info| { - const inst = try irb.genPtrType(prefix_op, ptr_info, scope); - return irb.lvalWrap(scope, inst, lval); - }, - .SliceType => |ptr_info| return error.Unimplemented, - .Try => return error.Unimplemented, - } - }, - .SuffixOp => { - const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", node); - switch (suffix_op.op) { - .Call => |*call| { - const inst = try irb.genCall(suffix_op, call, scope); - return irb.lvalWrap(scope, inst, lval); - }, - .ArrayAccess => |n| return error.Unimplemented, - .Slice => |slice| return error.Unimplemented, - .ArrayInitializer => |init_list| return error.Unimplemented, - .StructInitializer => |init_list| return error.Unimplemented, - .Deref => return error.Unimplemented, - .UnwrapOptional => return error.Unimplemented, - } - }, - .Switch => return error.Unimplemented, - .While => return error.Unimplemented, - .For => return error.Unimplemented, - .If => return error.Unimplemented, - .ControlFlowExpression => { - const control_flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", node); - return irb.genControlFlowExpr(control_flow_expr, scope, lval); - }, - .Suspend => return error.Unimplemented, - .VarType => return error.Unimplemented, - .ErrorType => return error.Unimplemented, - .FnProto => return error.Unimplemented, - .AnyFrameType => return error.Unimplemented, - .IntegerLiteral => { - const int_lit = @fieldParentPtr(ast.Node.IntegerLiteral, "base", node); - return irb.lvalWrap(scope, try irb.genIntLit(int_lit, scope), lval); - }, - .FloatLiteral => return error.Unimplemented, - .StringLiteral => { - const str_lit = @fieldParentPtr(ast.Node.StringLiteral, "base", node); - const inst = try irb.genStrLit(str_lit, scope); - return irb.lvalWrap(scope, inst, lval); - }, - .MultilineStringLiteral => return error.Unimplemented, - .CharLiteral => return error.Unimplemented, - .BoolLiteral => return error.Unimplemented, - .NullLiteral => return error.Unimplemented, - .UndefinedLiteral => return error.Unimplemented, - .Unreachable => return error.Unimplemented, - .Identifier => { - const identifier = @fieldParentPtr(ast.Node.Identifier, "base", node); - return irb.genIdentifier(identifier, scope, lval); - }, - .GroupedExpression => { - const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node); - return irb.genNodeRecursive(grouped_expr.expr, scope, lval); - }, - .BuiltinCall => return error.Unimplemented, - .ErrorSetDecl => return error.Unimplemented, - .ContainerDecl => return error.Unimplemented, - .Asm => return error.Unimplemented, - .Comptime => return error.Unimplemented, - .Block => { - const block = @fieldParentPtr(ast.Node.Block, "base", node); - const inst = try irb.genBlock(block, scope); - return irb.lvalWrap(scope, inst, lval); - }, - .DocComment => return error.Unimplemented, - .SwitchCase => return error.Unimplemented, - .SwitchElse => return error.Unimplemented, - .Else => return error.Unimplemented, - .Payload => return error.Unimplemented, - .PointerPayload => return error.Unimplemented, - .PointerIndexPayload => return error.Unimplemented, - .ContainerField => return error.Unimplemented, - .ErrorTag => return error.Unimplemented, - .AsmInput => return error.Unimplemented, - .AsmOutput => return error.Unimplemented, - .ParamDecl => return error.Unimplemented, - .FieldInitializer => return error.Unimplemented, - .EnumLiteral => return error.Unimplemented, - .Noasync => return error.Unimplemented, - } - } - - fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst { - const fn_ref = try irb.genNodeRecursive(suffix_op.lhs.node, scope, .None); - - const args = try irb.arena().alloc(*Inst, call.params.len); - var it = call.params.iterator(0); - var i: usize = 0; - while (it.next()) |arg_node_ptr| : (i += 1) { - args[i] = try irb.genNodeRecursive(arg_node_ptr.*, scope, .None); - } - - //bool is_async = node->data.fn_call_expr.is_async; - //IrInstruction *async_allocator = nullptr; - //if (is_async) { - // if (node->data.fn_call_expr.async_allocator) { - // async_allocator = ir_gen_node(irb, node->data.fn_call_expr.async_allocator, scope); - // if (async_allocator == irb->codegen->invalid_instruction) - // return async_allocator; - // } - //} - - return irb.build(Inst.Call, scope, Span.token(suffix_op.rtoken), Inst.Call.Params{ - .fn_ref = fn_ref, - .args = args, - }); - //IrInstruction *fn_call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto, is_async, async_allocator, nullptr); - //return ir_lval_wrap(irb, scope, fn_call, lval); - } - - fn genPtrType( - irb: *Builder, - prefix_op: *ast.Node.PrefixOp, - ptr_info: ast.Node.PrefixOp.PtrInfo, - scope: *Scope, - ) !*Inst { - // TODO port more logic - - //assert(node->type == NodeTypePointerType); - //PtrLen ptr_len = (node->data.pointer_type.star_token->id == TokenIdStar || - // node->data.pointer_type.star_token->id == TokenIdStarStar) ? PtrLenSingle : PtrLenUnknown; - //bool is_const = node->data.pointer_type.is_const; - //bool is_volatile = node->data.pointer_type.is_volatile; - //AstNode *expr_node = node->data.pointer_type.op_expr; - //AstNode *align_expr = node->data.pointer_type.align_expr; - - //IrInstruction *align_value; - //if (align_expr != nullptr) { - // align_value = ir_gen_node(irb, align_expr, scope); - // if (align_value == irb->codegen->invalid_instruction) - // return align_value; - //} else { - // align_value = nullptr; - //} - const child_type = try irb.genNodeRecursive(prefix_op.rhs, scope, .None); - - //uint32_t bit_offset_start = 0; - //if (node->data.pointer_type.bit_offset_start != nullptr) { - // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_start, 32, false)) { - // Buf *val_buf = buf_alloc(); - // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_start, 10); - // exec_add_error_node(irb->codegen, irb->exec, node, - // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf))); - // return irb->codegen->invalid_instruction; - // } - // bit_offset_start = bigint_as_unsigned(node->data.pointer_type.bit_offset_start); - //} - - //uint32_t bit_offset_end = 0; - //if (node->data.pointer_type.bit_offset_end != nullptr) { - // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_end, 32, false)) { - // Buf *val_buf = buf_alloc(); - // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_end, 10); - // exec_add_error_node(irb->codegen, irb->exec, node, - // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf))); - // return irb->codegen->invalid_instruction; - // } - // bit_offset_end = bigint_as_unsigned(node->data.pointer_type.bit_offset_end); - //} - - //if ((bit_offset_start != 0 || bit_offset_end != 0) && bit_offset_start >= bit_offset_end) { - // exec_add_error_node(irb->codegen, irb->exec, node, - // buf_sprintf("bit offset start must be less than bit offset end")); - // return irb->codegen->invalid_instruction; - //} - - return irb.build(Inst.PtrType, scope, Span.node(&prefix_op.base), Inst.PtrType.Params{ - .child_type = child_type, - .mut = .Mut, - .vol = .Non, - .size = .Many, - .alignment = null, - }); - } - - fn isCompTime(irb: *Builder, target_scope: *Scope) bool { - if (irb.is_comptime) - return true; - - var scope = target_scope; - while (true) { - switch (scope.id) { - .CompTime => return true, - .FnDef => return false, - .Decls => unreachable, - .Root => unreachable, - .AstTree => unreachable, - .Block, - .Defer, - .DeferExpr, - .Var, - => scope = scope.parent.?, - } - } - } - - pub fn genIntLit(irb: *Builder, int_lit: *ast.Node.IntegerLiteral, scope: *Scope) !*Inst { - const int_token = irb.code.tree_scope.tree.tokenSlice(int_lit.token); - - var base: u8 = undefined; - var rest: []const u8 = undefined; - if (int_token.len >= 3 and int_token[0] == '0') { - rest = int_token[2..]; - switch (int_token[1]) { - 'b' => base = 2, - 'o' => base = 8, - 'x' => base = 16, - else => { - base = 10; - rest = int_token; - }, - } - } else { - base = 10; - rest = int_token; - } - - const comptime_int_type = Type.ComptimeInt.get(irb.comp); - defer comptime_int_type.base.base.deref(irb.comp); - - const int_val = Value.Int.createFromString( - irb.comp, - &comptime_int_type.base, - base, - rest, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.InvalidBase => unreachable, - error.InvalidCharForDigit => unreachable, - error.DigitTooLargeForBase => unreachable, - }; - errdefer int_val.base.deref(irb.comp); - - const inst = try irb.build(Inst.Const, scope, Span.token(int_lit.token), Inst.Const.Params{}); - inst.val = IrVal{ .KnownValue = &int_val.base }; - return inst; - } - - pub fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst { - const str_token = irb.code.tree_scope.tree.tokenSlice(str_lit.token); - const src_span = Span.token(str_lit.token); - - var bad_index: usize = undefined; - var buf = std.zig.parseStringLiteral(irb.comp.gpa(), str_token, &bad_index) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.InvalidCharacter => { - try irb.comp.addCompileError( - irb.code.tree_scope, - src_span, - "invalid character in string literal: '{c}'", - .{str_token[bad_index]}, - ); - return error.SemanticAnalysisFailed; - }, - }; - var buf_cleaned = false; - errdefer if (!buf_cleaned) irb.comp.gpa().free(buf); - - if (str_token[0] == 'c') { - // first we add a null - buf = try irb.comp.gpa().realloc(buf, buf.len + 1); - buf[buf.len - 1] = 0; - - // next make an array value - const array_val = try Value.Array.createOwnedBuffer(irb.comp, buf); - buf_cleaned = true; - defer array_val.base.deref(irb.comp); - - // then make a pointer value pointing at the first element - const ptr_val = try Value.Ptr.createArrayElemPtr( - irb.comp, - array_val, - .Const, - .Many, - 0, - ); - defer ptr_val.base.deref(irb.comp); - - return irb.buildConstValue(scope, src_span, &ptr_val.base); - } else { - const array_val = try Value.Array.createOwnedBuffer(irb.comp, buf); - buf_cleaned = true; - defer array_val.base.deref(irb.comp); - - return irb.buildConstValue(scope, src_span, &array_val.base); - } - } - - pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst { - const block_scope = try Scope.Block.create(irb.comp, 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.?.block_scope == null) { - fndef_scope.fn_val.?.block_scope = block_scope; - } - } - - if (block.statements.len == 0) { - // {} - return irb.buildConstVoid(child_scope, Span.token(block.lbrace), false); - } - - if (block.label) |label| { - block_scope.incoming_values = std.ArrayList(*Inst).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 irb.buildConstBool( - parent_scope, - Span.token(block.lbrace), - irb.isCompTime(parent_scope), - ); - } - - var is_continuation_unreachable = false; - var noreturn_return_value: ?*Inst = 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.code.tree_scope.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.comp, parent_scope, defer_node.expr); - const defer_child_scope = try Scope.Defer.create(irb.comp, parent_scope, kind, defer_expr_scope); - child_scope = &defer_child_scope.base; - continue; - } - const statement_value = try irb.genNodeRecursive(statement_node, child_scope, .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(Inst.DeclVar)) |decl_var| { - // variable declarations start a new scope - child_scope = decl_var.params.variable.child_scope; - } else if (!is_continuation_unreachable) { - // this statement's value must be void - _ = try irb.build( - Inst.CheckVoidStmt, - child_scope, - Span{ - .first = statement_node.firstToken(), - .last = statement_node.lastToken(), - }, - Inst.CheckVoidStmt.Params{ .target = 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 irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{ - .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(), - .incoming_values = 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 irb.buildConstVoid(parent_scope, Span.token(block.rbrace), true), - ); - _ = try irb.genDefersForBlock(child_scope, outer_block_scope, .ScopeExit); - - _ = try irb.buildGen(Inst.Br, parent_scope, Span.token(block.rbrace), Inst.Br.Params{ - .dest_block = block_scope.end_block, - .is_comptime = block_scope.is_comptime, - }); - - try irb.setCursorAtEndAndAppendBlock(block_scope.end_block); - - return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{ - .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(), - .incoming_values = block_scope.incoming_values.toOwnedSlice(), - }); - } - - _ = try irb.genDefersForBlock(child_scope, outer_block_scope, .ScopeExit); - return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true); - } - - pub fn genControlFlowExpr( - irb: *Builder, - control_flow_expr: *ast.Node.ControlFlowExpression, - scope: *Scope, - lval: LVal, - ) !*Inst { - switch (control_flow_expr.kind) { - .Break => |arg| return error.Unimplemented, - .Continue => |arg| return error.Unimplemented, - .Return => { - const src_span = Span.token(control_flow_expr.ltoken); - if (scope.findFnDef() == null) { - try irb.comp.addCompileError( - irb.code.tree_scope, - src_span, - "return expression outside function definition", - .{}, - ); - return error.SemanticAnalysisFailed; - } - - if (scope.findDeferExpr()) |scope_defer_expr| { - if (!scope_defer_expr.reported_err) { - try irb.comp.addCompileError( - irb.code.tree_scope, - src_span, - "cannot return from defer expression", - .{}, - ); - scope_defer_expr.reported_err = true; - } - return error.SemanticAnalysisFailed; - } - - const outer_scope = irb.begin_scope.?; - const return_value = if (control_flow_expr.rhs) |rhs| blk: { - break :blk try irb.genNodeRecursive(rhs, scope, .None); - } else blk: { - break :blk try irb.buildConstVoid(scope, src_span, true); - }; - - const defer_counts = irb.countDefers(scope, outer_scope); - const have_err_defers = defer_counts.error_exit != 0; - if (have_err_defers or irb.comp.have_err_ret_tracing) { - const err_block = try irb.createBasicBlock(scope, "ErrRetErr"); - const ok_block = try irb.createBasicBlock(scope, "ErrRetOk"); - if (!have_err_defers) { - _ = try irb.genDefersForBlock(scope, outer_scope, .ScopeExit); - } - - const is_err = try irb.build( - Inst.TestErr, - scope, - src_span, - Inst.TestErr.Params{ .target = return_value }, - ); - - const err_is_comptime = try irb.buildTestCompTime(scope, src_span, is_err); - - _ = try irb.buildGen(Inst.CondBr, scope, src_span, Inst.CondBr.Params{ - .condition = is_err, - .then_block = err_block, - .else_block = ok_block, - .is_comptime = err_is_comptime, - }); - - const ret_stmt_block = try irb.createBasicBlock(scope, "RetStmt"); - - try irb.setCursorAtEndAndAppendBlock(err_block); - if (have_err_defers) { - _ = try irb.genDefersForBlock(scope, outer_scope, .ErrorExit); - } - if (irb.comp.have_err_ret_tracing and !irb.isCompTime(scope)) { - _ = try irb.build(Inst.SaveErrRetAddr, scope, src_span, Inst.SaveErrRetAddr.Params{}); - } - _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{ - .dest_block = ret_stmt_block, - .is_comptime = err_is_comptime, - }); - - try irb.setCursorAtEndAndAppendBlock(ok_block); - if (have_err_defers) { - _ = try irb.genDefersForBlock(scope, outer_scope, .ScopeExit); - } - _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{ - .dest_block = ret_stmt_block, - .is_comptime = err_is_comptime, - }); - - try irb.setCursorAtEndAndAppendBlock(ret_stmt_block); - return irb.genAsyncReturn(scope, src_span, return_value, false); - } else { - _ = try irb.genDefersForBlock(scope, outer_scope, .ScopeExit); - return irb.genAsyncReturn(scope, src_span, return_value, false); - } - }, - } - } - - pub fn genIdentifier(irb: *Builder, identifier: *ast.Node.Identifier, scope: *Scope, lval: LVal) !*Inst { - const src_span = Span.token(identifier.token); - const name = irb.code.tree_scope.tree.tokenSlice(identifier.token); - - //if (buf_eql_str(variable_name, "_") && lval == LValPtr) { - // IrInstructionConst *const_instruction = ir_build_instruction(irb, scope, node); - // const_instruction->base.value.type = get_pointer_to_type(irb->codegen, - // irb->codegen->builtin_types.entry_void, false); - // const_instruction->base.value.special = ConstValSpecialStatic; - // const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialDiscard; - // return &const_instruction->base; - //} - - if (irb.comp.getPrimitiveType(name)) |result| { - if (result) |primitive_type| { - defer primitive_type.base.deref(irb.comp); - switch (lval) { - // if (lval == LValPtr) { - // return ir_build_ref(irb, scope, node, value, false, false); - .Ptr => return error.Unimplemented, - .None => return irb.buildConstValue(scope, src_span, &primitive_type.base), - } - } - } else |err| switch (err) { - error.Overflow => { - try irb.comp.addCompileError(irb.code.tree_scope, src_span, "integer too large", .{}); - return error.SemanticAnalysisFailed; - }, - error.OutOfMemory => return error.OutOfMemory, - } - - switch (irb.findIdent(scope, name)) { - .Decl => |decl| { - return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{ - .decl = decl, - .lval = lval, - }); - }, - .VarScope => |var_scope| { - const var_ptr = try irb.build(Inst.VarPtr, scope, src_span, Inst.VarPtr.Params{ .var_scope = var_scope }); - switch (lval) { - .Ptr => return var_ptr, - .None => { - return irb.build(Inst.LoadPtr, scope, src_span, Inst.LoadPtr.Params{ .target = var_ptr }); - }, - } - }, - .NotFound => {}, - } - - //if (node->owner->any_imports_failed) { - // // skip the error message since we had a failing import in this file - // // if an import breaks we don't need redundant undeclared identifier errors - // return irb->codegen->invalid_instruction; - //} - - // TODO put a variable of same name with invalid type in global scope - // so that future references to this same name will find a variable with an invalid type - - try irb.comp.addCompileError(irb.code.tree_scope, src_span, "unknown identifier '{}'", .{name}); - return error.SemanticAnalysisFailed; - } - - const DeferCounts = struct { - scope_exit: usize, - error_exit: usize, - }; - - fn countDefers(irb: *Builder, inner_scope: *Scope, outer_scope: *Scope) DeferCounts { - var result = DeferCounts{ .scope_exit = 0, .error_exit = 0 }; - - var scope = inner_scope; - while (scope != outer_scope) { - switch (scope.id) { - .Defer => { - const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope); - switch (defer_scope.kind) { - .ScopeExit => result.scope_exit += 1, - .ErrorExit => result.error_exit += 1, - } - scope = scope.parent orelse break; - }, - .FnDef => break, - - .CompTime, - .Block, - .Decls, - .Root, - .Var, - => scope = scope.parent orelse break, - - .DeferExpr => unreachable, - .AstTree => unreachable, - } - } - 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) { - .Defer => { - const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope); - const generate = switch (defer_scope.kind) { - .ScopeExit => true, - .ErrorExit => gen_kind == .ErrorExit, - }; - if (generate) { - const defer_expr_scope = defer_scope.defer_expr_scope; - const instruction = try irb.genNodeRecursive( - defer_expr_scope.expr_node, - &defer_expr_scope.base, - .None, - ); - if (instruction.isNoReturn()) { - is_noreturn = true; - } else { - _ = try irb.build( - Inst.CheckVoidStmt, - &defer_expr_scope.base, - Span.token(defer_expr_scope.expr_node.lastToken()), - Inst.CheckVoidStmt.Params{ .target = instruction }, - ); - } - } - }, - .FnDef, - .Decls, - .Root, - => return is_noreturn, - - .CompTime, - .Block, - .Var, - => scope = scope.parent orelse return is_noreturn, - - .DeferExpr => unreachable, - .AstTree => unreachable, - } - } - } - - pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Inst, lval: LVal) !*Inst { - switch (lval) { - .None => return instruction, - .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 irb.build(Inst.Ref, scope, instruction.span, Inst.Ref.Params{ - .target = instruction, - .mut = .Const, - .volatility = .Non, - }); - }, - } - } - - fn arena(self: *Builder) *Allocator { - return &self.code.arena.allocator; - } - - fn buildExtra( - self: *Builder, - comptime I: type, - scope: *Scope, - span: Span, - params: I.Params, - is_generated: bool, - ) !*Inst { - const inst = try self.arena().create(I); - inst.* = I{ - .base = Inst{ - .id = Inst.typeToId(I), - .is_generated = is_generated, - .scope = scope, - .debug_id = self.next_debug_id, - .val = switch (I.ir_val_init) { - .Unknown => IrVal.Unknown, - .NoReturn => IrVal{ .KnownValue = &Value.NoReturn.get(self.comp).base }, - .Void => IrVal{ .KnownValue = &Value.Void.get(self.comp).base }, - }, - .ref_count = 0, - .span = span, - .child = null, - .parent = null, - .llvm_value = undefined, - .owner_bb = self.current_basic_block, - }, - .params = params, - }; - - // Look at the params and ref() other instructions - inline for (@typeInfo(I.Params).Struct.fields) |f| { - switch (f.field_type) { - *Inst => @field(inst.params, f.name).ref(self), - *BasicBlock => @field(inst.params, f.name).ref(self), - ?*Inst => if (@field(inst.params, f.name)) |other| other.ref(self), - []*Inst => { - // TODO https://github.com/ziglang/zig/issues/1269 - for (@field(inst.params, f.name)) |other| - other.ref(self); - }, - []*BasicBlock => { - // TODO https://github.com/ziglang/zig/issues/1269 - for (@field(inst.params, f.name)) |other| - other.ref(self); - }, - Type.Pointer.Mut, - Type.Pointer.Vol, - Type.Pointer.Size, - LVal, - *Decl, - *Scope.Var, - => {}, - // it's ok to add more types here, just make sure that - // any instructions and basic blocks are ref'd appropriately - else => @compileError("unrecognized type in Params: " ++ @typeName(f.field_type)), - } - } - - self.next_debug_id += 1; - try self.current_basic_block.instruction_list.append(&inst.base); - return &inst.base; - } - - fn build( - self: *Builder, - comptime I: type, - scope: *Scope, - span: Span, - params: I.Params, - ) !*Inst { - return self.buildExtra(I, scope, span, params, false); - } - - fn buildGen( - self: *Builder, - comptime I: type, - scope: *Scope, - span: Span, - params: I.Params, - ) !*Inst { - return self.buildExtra(I, scope, span, params, true); - } - - fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Inst { - const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{}); - inst.val = IrVal{ .KnownValue = &Value.Bool.get(self.comp, x).base }; - return inst; - } - - fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Inst { - const inst = try self.buildExtra(Inst.Const, scope, span, Inst.Const.Params{}, is_generated); - inst.val = IrVal{ .KnownValue = &Value.Void.get(self.comp).base }; - return inst; - } - - fn buildConstValue(self: *Builder, scope: *Scope, span: Span, v: *Value) !*Inst { - const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{}); - inst.val = IrVal{ .KnownValue = v.getRef() }; - return inst; - } - - /// If the code is explicitly set to be comptime, then builds a const bool, - /// otherwise builds a TestCompTime instruction. - fn buildTestCompTime(self: *Builder, scope: *Scope, span: Span, target: *Inst) !*Inst { - if (self.isCompTime(scope)) { - return self.buildConstBool(scope, span, true); - } else { - return self.build( - Inst.TestCompTime, - scope, - span, - Inst.TestCompTime.Params{ .target = target }, - ); - } - } - - fn genAsyncReturn(irb: *Builder, scope: *Scope, span: Span, result: *Inst, is_gen: bool) !*Inst { - _ = try irb.buildGen( - Inst.AddImplicitReturnType, - scope, - span, - Inst.AddImplicitReturnType.Params{ .target = result }, - ); - - if (!irb.is_async) { - return irb.buildExtra( - Inst.Return, - scope, - span, - Inst.Return.Params{ .return_value = result }, - is_gen, - ); - } - return error.Unimplemented; - } - - const Ident = union(enum) { - NotFound, - Decl: *Decl, - VarScope: *Scope.Var, - }; - - fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident { - var s = scope; - while (true) { - switch (s.id) { - .Root => return .NotFound, - .Decls => { - const decls = @fieldParentPtr(Scope.Decls, "base", s); - const locked_table = decls.table.acquireRead(); - defer locked_table.release(); - if (locked_table.value.get(name)) |entry| { - return Ident{ .Decl = entry.value }; - } - }, - .Var => { - const var_scope = @fieldParentPtr(Scope.Var, "base", s); - if (mem.eql(u8, var_scope.name, name)) { - return Ident{ .VarScope = var_scope }; - } - }, - else => {}, - } - s = s.parent.?; - } - } -}; - -const Analyze = struct { - irb: Builder, - old_bb_index: usize, - const_predecessor_bb: ?*BasicBlock, - parent_basic_block: *BasicBlock, - instruction_index: usize, - src_implicit_return_type_list: std.ArrayList(*Inst), - explicit_return_type: ?*Type, - - pub const Error = error{ - /// This is only for when we have already reported a compile error. It is the poison value. - SemanticAnalysisFailed, - - /// This is a placeholder - it is useful to use instead of panicking but once the compiler is - /// done this error code will be removed. - Unimplemented, - - OutOfMemory, - }; - - pub fn init(comp: *Compilation, tree_scope: *Scope.AstTree, explicit_return_type: ?*Type) !Analyze { - var irb = try Builder.init(comp, tree_scope, null); - errdefer irb.abort(); - - return Analyze{ - .irb = irb, - .old_bb_index = 0, - .const_predecessor_bb = null, - .parent_basic_block = undefined, // initialized with startBasicBlock - .instruction_index = undefined, // initialized with startBasicBlock - .src_implicit_return_type_list = std.ArrayList(*Inst).init(irb.arena()), - .explicit_return_type = explicit_return_type, - }; - } - - pub fn abort(self: *Analyze) void { - self.irb.abort(); - } - - pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Inst) !*BasicBlock { - if (old_bb.child) |child| { - if (ref_old_instruction == null or child.ref_instruction != ref_old_instruction) - return child; - } - - const new_bb = try self.irb.createBasicBlock(old_bb.scope, old_bb.name_hint); - new_bb.linkToParent(old_bb); - new_bb.ref_instruction = ref_old_instruction; - return new_bb; - } - - pub fn startBasicBlock(self: *Analyze, old_bb: *BasicBlock, const_predecessor_bb: ?*BasicBlock) void { - self.instruction_index = 0; - self.parent_basic_block = old_bb; - self.const_predecessor_bb = const_predecessor_bb; - } - - pub fn finishBasicBlock(ira: *Analyze, old_code: *Code) !void { - try ira.irb.code.basic_block_list.append(ira.irb.current_basic_block); - ira.instruction_index += 1; - - while (ira.instruction_index < ira.parent_basic_block.instruction_list.len) { - const next_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index); - - if (!next_instruction.is_generated) { - try ira.addCompileError(next_instruction.span, "unreachable code", .{}); - break; - } - ira.instruction_index += 1; - } - - ira.old_bb_index += 1; - - var need_repeat = true; - while (true) { - while (ira.old_bb_index < old_code.basic_block_list.len) { - const old_bb = old_code.basic_block_list.at(ira.old_bb_index); - const new_bb = old_bb.child orelse { - ira.old_bb_index += 1; - continue; - }; - if (new_bb.instruction_list.len != 0) { - ira.old_bb_index += 1; - continue; - } - ira.irb.current_basic_block = new_bb; - - ira.startBasicBlock(old_bb, null); - return; - } - if (!need_repeat) - return; - need_repeat = false; - ira.old_bb_index = 0; - continue; - } - } - - fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: var) !void { - return self.irb.comp.addCompileError(self.irb.code.tree_scope, span, fmt, args); - } - - fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Inst) Analyze.Error!*Type { - // TODO actual implementation - return &Type.Void.get(self.irb.comp).base; - } - - fn implicitCast(self: *Analyze, target: *Inst, optional_dest_type: ?*Type) Analyze.Error!*Inst { - const dest_type = optional_dest_type orelse return target; - const from_type = target.getKnownType(); - if (from_type == dest_type or from_type.id == .NoReturn) return target; - return self.analyzeCast(target, target, dest_type); - } - - fn analyzeCast(ira: *Analyze, source_instr: *Inst, target: *Inst, dest_type: *Type) !*Inst { - const from_type = target.getKnownType(); - - //if (type_is_invalid(wanted_type) || type_is_invalid(actual_type)) { - // return ira->codegen->invalid_instruction; - //} - - //// perfect match or non-const to const - //ConstCastOnly const_cast_result = types_match_const_cast_only(ira, wanted_type, actual_type, - // source_node, false); - //if (const_cast_result.id == ConstCastResultIdOk) { - // return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop, false); - //} - - //// widening conversion - //if (wanted_type->id == TypeTableEntryIdInt && - // actual_type->id == TypeTableEntryIdInt && - // wanted_type->data.integral.is_signed == actual_type->data.integral.is_signed && - // wanted_type->data.integral.bit_count >= actual_type->data.integral.bit_count) - //{ - // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type); - //} - - //// small enough unsigned ints can get casted to large enough signed ints - //if (wanted_type->id == TypeTableEntryIdInt && wanted_type->data.integral.is_signed && - // actual_type->id == TypeTableEntryIdInt && !actual_type->data.integral.is_signed && - // wanted_type->data.integral.bit_count > actual_type->data.integral.bit_count) - //{ - // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type); - //} - - //// float widening conversion - //if (wanted_type->id == TypeTableEntryIdFloat && - // actual_type->id == TypeTableEntryIdFloat && - // wanted_type->data.floating.bit_count >= actual_type->data.floating.bit_count) - //{ - // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type); - //} - - //// cast from [N]T to []const T - //if (is_slice(wanted_type) && actual_type->id == TypeTableEntryIdArray) { - // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; - // assert(ptr_type->id == TypeTableEntryIdPointer); - // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, - // source_node, false).id == ConstCastResultIdOk) - // { - // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type); - // } - //} - - //// cast from *const [N]T to []const T - //if (is_slice(wanted_type) && - // actual_type->id == TypeTableEntryIdPointer && - // actual_type->data.pointer.is_const && - // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray) - //{ - // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; - // assert(ptr_type->id == TypeTableEntryIdPointer); - - // TypeTableEntry *array_type = actual_type->data.pointer.child_type; - - // if ((ptr_type->data.pointer.is_const || array_type->data.array.len == 0) && - // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, array_type->data.array.child_type, - // source_node, false).id == ConstCastResultIdOk) - // { - // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type); - // } - //} - - //// cast from [N]T to *const []const T - //if (wanted_type->id == TypeTableEntryIdPointer && - // wanted_type->data.pointer.is_const && - // is_slice(wanted_type->data.pointer.child_type) && - // actual_type->id == TypeTableEntryIdArray) - //{ - // TypeTableEntry *ptr_type = - // wanted_type->data.pointer.child_type->data.structure.fields[slice_ptr_index].type_entry; - // assert(ptr_type->id == TypeTableEntryIdPointer); - // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, - // source_node, false).id == ConstCastResultIdOk) - // { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } - //} - - //// cast from [N]T to ?[]const T - //if (wanted_type->id == TypeTableEntryIdOptional && - // is_slice(wanted_type->data.maybe.child_type) && - // actual_type->id == TypeTableEntryIdArray) - //{ - // TypeTableEntry *ptr_type = - // wanted_type->data.maybe.child_type->data.structure.fields[slice_ptr_index].type_entry; - // assert(ptr_type->id == TypeTableEntryIdPointer); - // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, - // source_node, false).id == ConstCastResultIdOk) - // { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.maybe.child_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } - //} - - //// *[N]T to [*]T - //if (wanted_type->id == TypeTableEntryIdPointer && - // wanted_type->data.pointer.ptr_len == PtrLenUnknown && - // actual_type->id == TypeTableEntryIdPointer && - // actual_type->data.pointer.ptr_len == PtrLenSingle && - // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray && - // actual_type->data.pointer.alignment >= wanted_type->data.pointer.alignment && - // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type, - // actual_type->data.pointer.child_type->data.array.child_type, source_node, - // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk) - //{ - // return ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, source_instr, value, wanted_type); - //} - - //// *[N]T to []T - //if (is_slice(wanted_type) && - // actual_type->id == TypeTableEntryIdPointer && - // actual_type->data.pointer.ptr_len == PtrLenSingle && - // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray) - //{ - // TypeTableEntry *slice_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; - // assert(slice_ptr_type->id == TypeTableEntryIdPointer); - // if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, - // actual_type->data.pointer.child_type->data.array.child_type, source_node, - // !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk) - // { - // return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type); - // } - //} - - //// cast from T to ?T - //// note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism - //if (wanted_type->id == TypeTableEntryIdOptional) { - // TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type; - // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node, - // false).id == ConstCastResultIdOk) - // { - // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); - // } else if (actual_type->id == TypeTableEntryIdComptimeInt || - // actual_type->id == TypeTableEntryIdComptimeFloat) - // { - // if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) { - // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type); - // } else { - // return ira->codegen->invalid_instruction; - // } - // } else if (wanted_child_type->id == TypeTableEntryIdPointer && - // wanted_child_type->data.pointer.is_const && - // (actual_type->id == TypeTableEntryIdPointer || is_container(actual_type))) - // { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } - //} - - //// cast from null literal to maybe type - //if (wanted_type->id == TypeTableEntryIdOptional && - // actual_type->id == TypeTableEntryIdNull) - //{ - // return ir_analyze_null_to_maybe(ira, source_instr, value, wanted_type); - //} - - //// cast from child type of error type to error type - //if (wanted_type->id == TypeTableEntryIdErrorUnion) { - // if (types_match_const_cast_only(ira, wanted_type->data.error_union.payload_type, actual_type, - // source_node, false).id == ConstCastResultIdOk) - // { - // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type); - // } else if (actual_type->id == TypeTableEntryIdComptimeInt || - // actual_type->id == TypeTableEntryIdComptimeFloat) - // { - // if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.error_union.payload_type, true)) { - // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type); - // } else { - // return ira->codegen->invalid_instruction; - // } - // } - //} - - //// cast from [N]T to E![]const T - //if (wanted_type->id == TypeTableEntryIdErrorUnion && - // is_slice(wanted_type->data.error_union.payload_type) && - // actual_type->id == TypeTableEntryIdArray) - //{ - // TypeTableEntry *ptr_type = - // wanted_type->data.error_union.payload_type->data.structure.fields[slice_ptr_index].type_entry; - // assert(ptr_type->id == TypeTableEntryIdPointer); - // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) && - // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type, - // source_node, false).id == ConstCastResultIdOk) - // { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } - //} - - //// cast from error set to error union type - //if (wanted_type->id == TypeTableEntryIdErrorUnion && - // actual_type->id == TypeTableEntryIdErrorSet) - //{ - // return ir_analyze_err_wrap_code(ira, source_instr, value, wanted_type); - //} - - //// cast from T to E!?T - //if (wanted_type->id == TypeTableEntryIdErrorUnion && - // wanted_type->data.error_union.payload_type->id == TypeTableEntryIdOptional && - // actual_type->id != TypeTableEntryIdOptional) - //{ - // TypeTableEntry *wanted_child_type = wanted_type->data.error_union.payload_type->data.maybe.child_type; - // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node, false).id == ConstCastResultIdOk || - // actual_type->id == TypeTableEntryIdNull || - // actual_type->id == TypeTableEntryIdComptimeInt || - // actual_type->id == TypeTableEntryIdComptimeFloat) - // { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } - //} - - // cast from comptime-known integer to another integer where the value fits - if (target.isCompTime() and (from_type.id == .Int or from_type.id == .ComptimeInt)) cast: { - const target_val = target.val.KnownValue; - const from_int = &target_val.cast(Value.Int).?.big_int; - const fits = fits: { - if (dest_type.cast(Type.ComptimeInt)) |ctint| { - break :fits true; - } - if (dest_type.cast(Type.Int)) |int| { - break :fits from_int.fitsInTwosComp(int.key.is_signed, int.key.bit_count); - } - break :cast; - }; - if (!fits) { - try ira.addCompileError(source_instr.span, "integer value '{}' cannot be stored in type '{}'", .{ - from_int, - dest_type.name, - }); - return error.SemanticAnalysisFailed; - } - - const new_val = try target.copyVal(ira.irb.comp); - new_val.setType(dest_type, ira.irb.comp); - return ira.irb.buildConstValue(source_instr.scope, source_instr.span, new_val); - } - - // cast from number literal to another type - // cast from number literal to *const integer - //if (actual_type->id == TypeTableEntryIdComptimeFloat || - // actual_type->id == TypeTableEntryIdComptimeInt) - //{ - // ensure_complete_type(ira->codegen, wanted_type); - // if (type_is_invalid(wanted_type)) - // return ira->codegen->invalid_instruction; - // if (wanted_type->id == TypeTableEntryIdEnum) { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.enumeration.tag_int_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } else if (wanted_type->id == TypeTableEntryIdPointer && - // wanted_type->data.pointer.is_const) - // { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } else if (ir_num_lit_fits_in_other_type(ira, value, wanted_type, true)) { - // CastOp op; - // if ((actual_type->id == TypeTableEntryIdComptimeFloat && - // wanted_type->id == TypeTableEntryIdFloat) || - // (actual_type->id == TypeTableEntryIdComptimeInt && - // wanted_type->id == TypeTableEntryIdInt)) - // { - // op = CastOpNumLitToConcrete; - // } else if (wanted_type->id == TypeTableEntryIdInt) { - // op = CastOpFloatToInt; - // } else if (wanted_type->id == TypeTableEntryIdFloat) { - // op = CastOpIntToFloat; - // } else { - // zig_unreachable(); - // } - // return ir_resolve_cast(ira, source_instr, value, wanted_type, op, false); - // } else { - // return ira->codegen->invalid_instruction; - // } - //} - - //// cast from typed number to integer or float literal. - //// works when the number is known at compile time - //if (instr_is_comptime(value) && - // ((actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdComptimeInt) || - // (actual_type->id == TypeTableEntryIdFloat && wanted_type->id == TypeTableEntryIdComptimeFloat))) - //{ - // return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type); - //} - - //// cast from union to the enum type of the union - //if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) { - // type_ensure_zero_bits_known(ira->codegen, actual_type); - // if (type_is_invalid(actual_type)) - // return ira->codegen->invalid_instruction; - - // if (actual_type->data.unionation.tag_type == wanted_type) { - // return ir_analyze_union_to_tag(ira, source_instr, value, wanted_type); - // } - //} - - //// enum to union which has the enum as the tag type - //if (wanted_type->id == TypeTableEntryIdUnion && actual_type->id == TypeTableEntryIdEnum && - // (wanted_type->data.unionation.decl_node->data.container_decl.auto_enum || - // wanted_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)) - //{ - // type_ensure_zero_bits_known(ira->codegen, wanted_type); - // if (wanted_type->data.unionation.tag_type == actual_type) { - // return ir_analyze_enum_to_union(ira, source_instr, value, wanted_type); - // } - //} - - //// enum to &const union which has the enum as the tag type - //if (actual_type->id == TypeTableEntryIdEnum && wanted_type->id == TypeTableEntryIdPointer) { - // TypeTableEntry *union_type = wanted_type->data.pointer.child_type; - // if (union_type->data.unionation.decl_node->data.container_decl.auto_enum || - // union_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr) - // { - // type_ensure_zero_bits_known(ira->codegen, union_type); - // if (union_type->data.unionation.tag_type == actual_type) { - // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, union_type, value); - // if (type_is_invalid(cast1->value.type)) - // return ira->codegen->invalid_instruction; - - // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1); - // if (type_is_invalid(cast2->value.type)) - // return ira->codegen->invalid_instruction; - - // return cast2; - // } - // } - //} - - //// cast from *T to *[1]T - //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle && - // actual_type->id == TypeTableEntryIdPointer && actual_type->data.pointer.ptr_len == PtrLenSingle) - //{ - // TypeTableEntry *array_type = wanted_type->data.pointer.child_type; - // if (array_type->id == TypeTableEntryIdArray && array_type->data.array.len == 1 && - // types_match_const_cast_only(ira, array_type->data.array.child_type, - // actual_type->data.pointer.child_type, source_node, - // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk) - // { - // if (wanted_type->data.pointer.alignment > actual_type->data.pointer.alignment) { - // ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("cast increases pointer alignment")); - // add_error_note(ira->codegen, msg, value->source_node, - // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&actual_type->name), - // actual_type->data.pointer.alignment)); - // add_error_note(ira->codegen, msg, source_instr->source_node, - // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&wanted_type->name), - // wanted_type->data.pointer.alignment)); - // return ira->codegen->invalid_instruction; - // } - // return ir_analyze_ptr_to_array(ira, source_instr, value, wanted_type); - // } - //} - - //// cast from T to *T where T is zero bits - //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle && - // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type, - // actual_type, source_node, !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk) - //{ - // type_ensure_zero_bits_known(ira->codegen, actual_type); - // if (type_is_invalid(actual_type)) { - // return ira->codegen->invalid_instruction; - // } - // if (!type_has_bits(actual_type)) { - // return ir_get_ref(ira, source_instr, value, false, false); - // } - //} - - //// cast from undefined to anything - //if (actual_type->id == TypeTableEntryIdUndefined) { - // return ir_analyze_undefined_to_anything(ira, source_instr, value, wanted_type); - //} - - //// cast from something to const pointer of it - //if (!type_requires_comptime(actual_type)) { - // TypeTableEntry *const_ptr_actual = get_pointer_to_type(ira->codegen, actual_type, true); - // if (types_match_const_cast_only(ira, wanted_type, const_ptr_actual, source_node, false).id == ConstCastResultIdOk) { - // return ir_analyze_cast_ref(ira, source_instr, value, wanted_type); - // } - //} - - try ira.addCompileError(source_instr.span, "expected type '{}', found '{}'", .{ - dest_type.name, - from_type.name, - }); - //ErrorMsg *parent_msg = ir_add_error_node(ira, source_instr->source_node, - // buf_sprintf("expected type '%s', found '%s'", - // buf_ptr(&wanted_type->name), - // buf_ptr(&actual_type->name))); - //report_recursive_error(ira, source_instr->source_node, &const_cast_result, parent_msg); - return error.SemanticAnalysisFailed; - } - - fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Inst) ?*Value { - @panic("TODO"); - } - - fn getCompTimeRef( - self: *Analyze, + /// This struct owns the `Value` memory. When the struct is deallocated, + /// so is the `Value`. The value of a constant must be copied into + /// a memory location for the value to survive after a const instruction. + pub const Constant = struct { + base: Inst = Inst{ .tag = .constant }, value: *Value, - ptr_mut: Value.Ptr.Mut, - mut: Type.Pointer.Mut, - volatility: Type.Pointer.Vol, - ) Analyze.Error!*Inst { - return error.Unimplemented; - } + }; + + pub const PtrToInt = struct { + base: Inst = Inst{ .tag = .ptrtoint }, + }; + + pub const FieldPtr = struct { + base: Inst = Inst{ .tag = .fieldptr }, + }; + + pub const Deref = struct { + base: Inst = Inst{ .tag = .deref }, + }; + + pub const Assembly = struct { + base: Inst = Inst{ .tag = .@"asm" }, + }; + + pub const Unreach = struct { + base: Inst = Inst{ .tag = .unreach }, + }; }; -pub fn gen( - comp: *Compilation, - body_node: *ast.Node, - tree_scope: *Scope.AstTree, - scope: *Scope, -) !*Code { - var irb = try Builder.init(comp, tree_scope, scope); - errdefer irb.abort(); +pub const ErrorMsg = struct { + byte_offset: usize, + msg: []const u8, +}; - const entry_block = try irb.createBasicBlock(scope, "Entry"); - entry_block.ref(&irb); // Entry block gets a reference because we enter it to begin. - try irb.setCursorAtEndAndAppendBlock(entry_block); +pub const Tree = struct { + decls: std.ArrayList(*Inst), + errors: std.ArrayList(ErrorMsg), +}; - const result = try irb.genNode(body_node, scope, .None); - if (!result.isNoReturn()) { - // no need for save_err_ret_addr because this cannot return error - _ = try irb.genAsyncReturn(scope, Span.token(body_node.lastToken()), result, true); - } +const ParseContext = struct { + allocator: *Allocator, + i: usize, + source: []const u8, + errors: *std.ArrayList(ErrorMsg), +}; - return irb.finish(); +pub fn parse(allocator: *Allocator, source: []const u8) Allocator.Error!Tree { + var tree: Tree = .{ + .decls = std.ArrayList(*Inst).init(allocator), + .errors = std.ArrayList(ErrorMsg).init(allocator), + }; + var ctx: ParseContext = .{ + .allocator = allocator, + .i = 0, + .source = source, + .errors = &tree.errors, + }; + parseRoot(&ctx, &tree) catch |err| switch (err) { + error.ParseFailure => { + assert(tree.errors.items.len != 0); + }, + else => |e| return e, + }; + return tree; } -pub fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code { - const old_entry_bb = old_code.basic_block_list.at(0); - - var ira = try Analyze.init(comp, old_code.tree_scope, expected_type); - errdefer ira.abort(); - - const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null); - new_entry_bb.ref(&ira.irb); - - ira.irb.current_basic_block = new_entry_bb; - - ira.startBasicBlock(old_entry_bb, null); - - while (ira.old_bb_index < old_code.basic_block_list.len) { - const old_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index); - - if (old_instruction.ref_count == 0 and !old_instruction.hasSideEffects()) { - ira.instruction_index += 1; +pub fn parseRoot(ctx: *ParseContext, tree: *Tree) !void { + // The IR format is designed so that it can be tokenized and parsed at the same time. + var global_name_map = std.StringHashMap(usize).init(ctx.allocator); + while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { + ';' => _ = try skipToAndOver(ctx, '\n'), + '@' => { + const at_start = ctx.i; + const ident = try skipToAndOver(ctx, ' '); + var ty: ?*Value = null; + if (eatByte(ctx, ':')) { + ty = try parseType(ctx); + skipSpace(ctx); + } + try requireEatBytes(ctx, "= "); + const inst = try parseInstruction(ctx); + const ident_index = tree.decls.items.len; + if (try global_name_map.put(ident, ident_index)) |_| { + return parseError(ctx, "redefinition of identifier '{}'", .{ident}); + } + try tree.decls.append(inst); continue; - } - - const return_inst = try old_instruction.analyze(&ira); - assert(return_inst.val != IrVal.Unknown); // at least the type should be known at this point - return_inst.linkToParent(old_instruction); - // Note: if we ever modify the above to handle error.CompileError by continuing analysis, - // then here we want to check if ira.isCompTime() and return early if true - - if (return_inst.isNoReturn()) { - try ira.finishBasicBlock(old_code); - continue; - } - - ira.instruction_index += 1; - } - - if (ira.src_implicit_return_type_list.len == 0) { - ira.irb.code.return_type = &Type.NoReturn.get(comp).base; - return ira.irb.finish(); - } - - ira.irb.code.return_type = try ira.resolvePeerTypes(expected_type, ira.src_implicit_return_type_list.span()); - return ira.irb.finish(); + }, + ' ', '\n' => continue, + else => |byte| return parseError(ctx, "unexpected byte: '{c}'", .{byte}), + }; +} + +fn eatByte(ctx: *ParseContext, byte: u8) bool { + if (ctx.i >= ctx.source.len) return false; + if (ctx.source[ctx.i] != byte) return false; + ctx.i += 1; + return true; +} + +fn skipSpace(ctx: *ParseContext) void { + while (ctx.i < ctx.source.len and ctx.source[ctx.i] == ' ') : (ctx.i += 1) {} +} + +fn requireEatBytes(ctx: *ParseContext, bytes: []const u8) !void { + if (ctx.i + bytes.len > ctx.source.len) + return parseError(ctx, "unexpected EOF", .{}); + if (!mem.eql(u8, ctx.source[ctx.i..][0..bytes.len], bytes)) + return parseError(ctx, "expected '{}'", .{bytes}); + ctx.i += bytes.len; +} + +fn skipToAndOver(ctx: *ParseContext, byte: u8) ![]const u8 { + const start_i = ctx.i; + while (ctx.i < ctx.source.len) : (ctx.i += 1) { + if (ctx.source[ctx.i] == byte) { + const result = ctx.source[start_i..ctx.i]; + ctx.i += 1; + return result; + } + } + return parseError(ctx, "unexpected EOF", .{}); +} + +fn parseError(ctx: *ParseContext, comptime format: []const u8, args: var) error{ ParseFailure, OutOfMemory } { + const msg = try std.fmt.allocPrint(ctx.allocator, format, args); + (try ctx.errors.addOne()).* = .{ + .byte_offset = ctx.i, + .msg = msg, + }; + return error.ParseFailure; +} + +fn parseType(ctx: *ParseContext) !*Value { + return parseError(ctx, "TODO parse type", .{}); +} + +fn parseInstruction(ctx: *ParseContext) !*Inst { + switch (ctx.source[ctx.i]) { + '"' => return parseStringLiteralConst(ctx), + '0'...'9' => return parseIntegerLiteralConst(ctx), + else => {}, + } + const fn_name = skipToAndOver(ctx, '('); + return parseError(ctx, "TODO parse instruction '{}'", .{fn_name}); +} + +fn parseStringLiteralConst(ctx: *ParseContext) !*Inst { + const start = ctx.i; + ctx.i += 1; // skip over '"' + + while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { + '"' => { + ctx.i += 1; + const span = ctx.source[start..ctx.i]; + var bad_index: usize = undefined; + const parsed = std.zig.parseStringLiteral(ctx.allocator, span, &bad_index) catch |err| switch (err) { + error.InvalidCharacter => { + ctx.i = start + bad_index; + const bad_byte = ctx.source[ctx.i]; + return parseError(ctx, "invalid string literal character: '{c}'\n", .{bad_byte}); + }, + else => |e| return e, + }; + const bytes_val = try ctx.allocator.create(Value.Bytes); + bytes_val.* = .{ .data = parsed }; + const const_inst = try ctx.allocator.create(Inst.Constant); + const_inst.* = .{ .value = &bytes_val.base }; + return &const_inst.base; + }, + '\\' => { + ctx.i += 1; + if (ctx.i >= ctx.source.len) break; + continue; + }, + else => continue, + }; + return parseError(ctx, "unexpected EOF in string literal", .{}); +} + +fn parseIntegerLiteralConst(ctx: *ParseContext) !*Inst { + return parseError(ctx, "TODO parse integer literal", .{}); +} + +pub fn main() anyerror!void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = &arena.allocator; + + const args = try std.process.argsAlloc(allocator); + + const src_path = args[1]; + const debug_error_trace = true; + + const source = try std.fs.cwd().readFileAlloc(allocator, src_path, std.math.maxInt(u32)); + + const tree = try parse(allocator, source); + if (tree.errors.items.len != 0) { + for (tree.errors.items) |err_msg| { + const loc = findLineColumn(source, err_msg.byte_offset); + std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); + } + if (debug_error_trace) return error.ParseFailure; + std.process.exit(1); + } +} + +fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } { + var line: usize = 0; + var column: usize = 0; + for (source[0..byte_offset]) |byte| { + switch (byte) { + '\n' => { + line += 1; + column = 0; + }, + else => { + column += 1; + }, + } + } + return .{ .line = line, .column = column }; } diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index c35289e23..3aa451045 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -1,587 +1,86 @@ const std = @import("std"); -const Scope = @import("scope.zig").Scope; -const Compilation = @import("compilation.zig").Compilation; -const ObjectFile = @import("codegen.zig").ObjectFile; -const llvm = @import("llvm.zig"); -const ArrayListSentineled = std.ArrayListSentineled; -const assert = std.debug.assert; -/// Values are ref-counted, heap-allocated, and copy-on-write -/// If there is only 1 ref then write need not copy +/// This is the raw data, with no bookkeeping, no memory awareness, +/// no de-duplication, and no type system awareness. +/// It's important for this struct to be small. +/// It is not copyable since it may contain references to its inner data. pub const Value = struct { - id: Id, - typ: *Type, - ref_count: std.atomic.Int(usize), + tag: Tag, - /// Thread-safe - pub fn ref(base: *Value) void { - _ = base.ref_count.incr(); - } + pub const Tag = enum { + void_type, + noreturn_type, + bool_type, + usize_type, - /// Thread-safe - pub fn deref(base: *Value, comp: *Compilation) void { - if (base.ref_count.decr() == 1) { - base.typ.base.deref(comp); - switch (base.id) { - .Type => @fieldParentPtr(Type, "base", base).destroy(comp), - .Fn => @fieldParentPtr(Fn, "base", base).destroy(comp), - .FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp), - .Void => @fieldParentPtr(Void, "base", base).destroy(comp), - .Bool => @fieldParentPtr(Bool, "base", base).destroy(comp), - .NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp), - .Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp), - .Int => @fieldParentPtr(Int, "base", base).destroy(comp), - .Array => @fieldParentPtr(Array, "base", base).destroy(comp), - } - } - } + void_value, + noreturn_value, + bool_true, + bool_false, - pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void { - base.typ.base.deref(comp); - new_type.base.ref(); - base.typ = new_type; - } + array_sentinel_0_u8_type, + single_const_ptr_type, - pub fn getRef(base: *Value) *Value { - base.ref(); - return base; - } - - pub fn cast(base: *Value, comptime T: type) ?*T { - if (base.id != @field(Id, @typeName(T))) return null; - return @fieldParentPtr(T, "base", base); - } - - pub fn dump(base: *const Value) void { - std.debug.warn("{}", .{@tagName(base.id)}); - } - - pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?*llvm.Value) { - switch (base.id) { - .Type => unreachable, - .Fn => return @fieldParentPtr(Fn, "base", base).getLlvmConst(ofile), - .FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile), - .Void => return null, - .Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile), - .NoReturn => unreachable, - .Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile), - .Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile), - .Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile), - } - } - - pub fn derefAndCopy(self: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) { - if (self.ref_count.get() == 1) { - // ( ͡° ͜ʖ ͡°) - return self; - } - - assert(self.ref_count.decr() != 1); - return self.copy(comp); - } - - pub fn copy(base: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) { - switch (base.id) { - .Type => unreachable, - .Fn => unreachable, - .FnProto => unreachable, - .Void => unreachable, - .Bool => unreachable, - .NoReturn => unreachable, - .Ptr => unreachable, - .Array => unreachable, - .Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base, - } - } - - pub const Parent = union(enum) { - None, - BaseStruct: BaseStruct, - BaseArray: BaseArray, - BaseUnion: *Value, - BaseScalar: *Value, - - pub const BaseStruct = struct { - val: *Value, - field_index: usize, - }; - - pub const BaseArray = struct { - val: *Value, - elem_index: usize, - }; + int_u64, + int_i64, + function, + ref, + bytes, }; - pub const Id = enum { - Type, - Fn, - Void, - Bool, - NoReturn, - Array, - Ptr, - Int, - FnProto, + pub const Int_u64 = struct { + base: Value = Value{ .tag = .int_u64 }, + int: u64, }; - pub const Type = @import("type.zig").Type; - - pub const FnProto = struct { - base: Value, - - /// The main external name that is used in the .o file. - /// TODO https://github.com/ziglang/zig/issues/265 - symbol_name: ArrayListSentineled(u8, 0), - - pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: ArrayListSentineled(u8, 0)) !*FnProto { - const self = try comp.gpa().create(FnProto); - self.* = FnProto{ - .base = Value{ - .id = .FnProto, - .typ = &fn_type.base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .symbol_name = symbol_name, - }; - fn_type.base.base.ref(); - return self; - } - - pub fn destroy(self: *FnProto, comp: *Compilation) void { - self.symbol_name.deinit(); - comp.gpa().destroy(self); - } - - pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?*llvm.Value { - const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); - const llvm_fn = llvm.AddFunction( - ofile.module, - self.symbol_name.span(), - llvm_fn_type, - ) orelse return error.OutOfMemory; - - // TODO port more logic from codegen.cpp:fn_llvm_value - - return llvm_fn; - } + pub const Int_i64 = struct { + base: Value = Value{ .tag = .int_i64 }, + int: i64, }; - pub const Fn = struct { - base: Value, - - /// The main external name that is used in the .o file. - /// TODO https://github.com/ziglang/zig/issues/265 - symbol_name: ArrayListSentineled(u8, 0), - - /// 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, - - /// Path to the object file that contains this function - containing_object: ArrayListSentineled(u8, 0), - - link_set_node: *std.TailQueue(?*Value.Fn).Node, - - /// Creates a Fn value with 1 ref - /// Takes ownership of symbol_name - pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: ArrayListSentineled(u8, 0)) !*Fn { - const link_set_node = try comp.gpa().create(Compilation.FnLinkSet.Node); - link_set_node.* = Compilation.FnLinkSet.Node{ - .data = null, - .next = undefined, - .prev = undefined, - }; - errdefer comp.gpa().destroy(link_set_node); - - const self = try comp.gpa().create(Fn); - self.* = Fn{ - .base = Value{ - .id = .Fn, - .typ = &fn_type.base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .fndef_scope = fndef_scope, - .child_scope = &fndef_scope.base, - .block_scope = null, - .symbol_name = symbol_name, - .containing_object = ArrayListSentineled(u8, 0).initNull(comp.gpa()), - .link_set_node = link_set_node, - }; - fn_type.base.base.ref(); - fndef_scope.fn_val = self; - fndef_scope.base.ref(); - return self; - } - - pub fn destroy(self: *Fn, comp: *Compilation) void { - // remove with a tombstone so that we do not have to grab a lock - if (self.link_set_node.data != null) { - // it's now the job of the link step to find this tombstone and - // deallocate it. - self.link_set_node.data = null; - } else { - comp.gpa().destroy(self.link_set_node); - } - - self.containing_object.deinit(); - self.fndef_scope.base.deref(comp); - self.symbol_name.deinit(); - comp.gpa().destroy(self); - } - - /// We know that the function definition will end up in an .o file somewhere. - /// Here, all we have to do is generate a global prototype. - /// TODO cache the prototype per ObjectFile - pub fn getLlvmConst(self: *Fn, ofile: *ObjectFile) !?*llvm.Value { - const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); - const llvm_fn = llvm.AddFunction( - ofile.module, - self.symbol_name.span(), - llvm_fn_type, - ) orelse return error.OutOfMemory; - - // TODO port more logic from codegen.cpp:fn_llvm_value - - return llvm_fn; - } + pub const Function = struct { + base: Value = Value{ .tag = .function }, }; - pub const Void = struct { - base: Value, - - pub fn get(comp: *Compilation) *Void { - comp.void_value.base.ref(); - return comp.void_value; - } - - pub fn destroy(self: *Void, comp: *Compilation) void { - comp.gpa().destroy(self); - } + pub const ArraySentinel0_u8_Type = struct { + base: Value = Value{ .tag = .array_sentinel_0_u8_type }, + len: u64, }; - pub const Bool = struct { - base: Value, - x: bool, - - pub fn get(comp: *Compilation, x: bool) *Bool { - if (x) { - comp.true_value.base.ref(); - return comp.true_value; - } else { - comp.false_value.base.ref(); - return comp.false_value; - } - } - - pub fn destroy(self: *Bool, comp: *Compilation) void { - comp.gpa().destroy(self); - } - - pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) !?*llvm.Value { - const llvm_type = llvm.Int1TypeInContext(ofile.context) orelse return error.OutOfMemory; - if (self.x) { - return llvm.ConstAllOnes(llvm_type); - } else { - return llvm.ConstNull(llvm_type); - } - } + pub const SingleConstPtrType = struct { + base: Value = Value{ .tag = .single_const_ptr_type }, + elem_type: *Value, }; - pub const NoReturn = struct { - base: Value, - - pub fn get(comp: *Compilation) *NoReturn { - comp.noreturn_value.base.ref(); - return comp.noreturn_value; - } - - pub fn destroy(self: *NoReturn, comp: *Compilation) void { - comp.gpa().destroy(self); - } + pub const Ref = struct { + base: Value = Value{ .tag = .ref }, + pointee: *MemoryCell, }; - pub const Ptr = struct { - base: Value, - special: Special, - mut: Mut, - - pub const Mut = enum { - CompTimeConst, - CompTimeVar, - RunTime, - }; - - pub const Special = union(enum) { - Scalar: *Value, - BaseArray: BaseArray, - BaseStruct: BaseStruct, - HardCodedAddr: u64, - Discard, - }; - - pub const BaseArray = struct { - val: *Value, - elem_index: usize, - }; - - pub const BaseStruct = struct { - val: *Value, - field_index: usize, - }; - - pub fn createArrayElemPtr( - comp: *Compilation, - array_val: *Array, - mut: Type.Pointer.Mut, - size: Type.Pointer.Size, - elem_index: usize, - ) !*Ptr { - array_val.base.ref(); - errdefer array_val.base.deref(comp); - - const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type; - const ptr_type = try Type.Pointer.get(comp, Type.Pointer.Key{ - .child_type = elem_type, - .mut = mut, - .vol = Type.Pointer.Vol.Non, - .size = size, - .alignment = .Abi, - }); - var ptr_type_consumed = false; - errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp); - - const self = try comp.gpa().create(Value.Ptr); - self.* = Value.Ptr{ - .base = Value{ - .id = .Ptr, - .typ = &ptr_type.base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .special = Special{ - .BaseArray = BaseArray{ - .val = &array_val.base, - .elem_index = 0, - }, - }, - .mut = Mut.CompTimeConst, - }; - ptr_type_consumed = true; - errdefer comp.gpa().destroy(self); - - return self; - } - - pub fn destroy(self: *Ptr, comp: *Compilation) void { - comp.gpa().destroy(self); - } - - pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?*llvm.Value { - const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context); - // TODO carefully port the logic from codegen.cpp:gen_const_val_ptr - switch (self.special) { - .Scalar => |scalar| @panic("TODO"), - .BaseArray => |base_array| { - // TODO put this in one .o file only, and after that, generate extern references to it - const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?; - const ptr_bit_count = ofile.comp.target_ptr_bits; - const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory; - var indices = [_]*llvm.Value{ - llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory, - llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory, - }; - return llvm.ConstInBoundsGEP( - array_llvm_value, - @ptrCast([*]*llvm.Value, &indices), - @intCast(c_uint, indices.len), - ) orelse return error.OutOfMemory; - }, - .BaseStruct => |base_struct| @panic("TODO"), - .HardCodedAddr => |addr| @panic("TODO"), - .Discard => unreachable, - } - } - }; - - pub const Array = struct { - base: Value, - special: Special, - - pub const Special = union(enum) { - Undefined, - OwnedBuffer: []u8, - Explicit: Data, - }; - - pub const Data = struct { - parent: Parent, - elements: []*Value, - }; - - /// Takes ownership of buffer - pub fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array { - const u8_type = Type.Int.get_u8(comp); - defer u8_type.base.base.deref(comp); - - const array_type = try Type.Array.get(comp, Type.Array.Key{ - .elem_type = &u8_type.base, - .len = buffer.len, - }); - errdefer array_type.base.base.deref(comp); - - const self = try comp.gpa().create(Value.Array); - self.* = Value.Array{ - .base = Value{ - .id = .Array, - .typ = &array_type.base, - .ref_count = std.atomic.Int(usize).init(1), - }, - .special = Special{ .OwnedBuffer = buffer }, - }; - errdefer comp.gpa().destroy(self); - - return self; - } - - pub fn destroy(self: *Array, comp: *Compilation) void { - switch (self.special) { - .Undefined => {}, - .OwnedBuffer => |buf| { - comp.gpa().free(buf); - }, - .Explicit => {}, - } - comp.gpa().destroy(self); - } - - pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?*llvm.Value { - switch (self.special) { - .Undefined => { - const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context); - return llvm.GetUndef(llvm_type); - }, - .OwnedBuffer => |buf| { - const dont_null_terminate = 1; - const llvm_str_init = llvm.ConstStringInContext( - ofile.context, - buf.ptr, - @intCast(c_uint, buf.len), - dont_null_terminate, - ) orelse return error.OutOfMemory; - const str_init_type = llvm.TypeOf(llvm_str_init); - const global = llvm.AddGlobal(ofile.module, str_init_type, "") orelse return error.OutOfMemory; - llvm.SetInitializer(global, llvm_str_init); - llvm.SetLinkage(global, llvm.PrivateLinkage); - llvm.SetGlobalConstant(global, 1); - llvm.SetUnnamedAddr(global, 1); - llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type)); - return global; - }, - .Explicit => @panic("TODO"), - } - - //{ - // uint64_t len = type_entry->data.array.len; - // if (const_val->data.x_array.special == ConstArraySpecialUndef) { - // return LLVMGetUndef(type_entry->type_ref); - // } - - // LLVMValueRef *values = allocate(len); - // LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref; - // bool make_unnamed_struct = false; - // for (uint64_t i = 0; i < len; i += 1) { - // ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i]; - // LLVMValueRef val = gen_const_val(g, elem_value, ""); - // values[i] = val; - // make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val); - // } - // if (make_unnamed_struct) { - // return LLVMConstStruct(values, len, true); - // } else { - // return LLVMConstArray(element_type_ref, values, (unsigned)len); - // } - //} - } - }; - - pub const Int = struct { - base: Value, - big_int: std.math.big.Int, - - pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int { - const self = try comp.gpa().create(Value.Int); - self.* = Value.Int{ - .base = Value{ - .id = .Int, - .typ = typ, - .ref_count = std.atomic.Int(usize).init(1), - }, - .big_int = undefined, - }; - typ.base.ref(); - errdefer comp.gpa().destroy(self); - - self.big_int = try std.math.big.Int.init(comp.gpa()); - errdefer self.big_int.deinit(); - - try self.big_int.setString(base, value); - - return self; - } - - pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?*llvm.Value { - switch (self.base.typ.id) { - .Int => { - const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context); - if (self.big_int.len() == 0) { - return llvm.ConstNull(type_ref); - } - const unsigned_val = if (self.big_int.len() == 1) blk: { - break :blk llvm.ConstInt(type_ref, self.big_int.limbs[0], @boolToInt(false)); - } else if (@sizeOf(std.math.big.Limb) == @sizeOf(u64)) blk: { - break :blk llvm.ConstIntOfArbitraryPrecision( - type_ref, - @intCast(c_uint, self.big_int.len()), - @ptrCast([*]u64, self.big_int.limbs.ptr), - ); - } else { - @compileError("std.math.Big.Int.Limb size does not match LLVM"); - }; - return if (self.big_int.isPositive()) unsigned_val else llvm.ConstNeg(unsigned_val); - }, - .ComptimeInt => unreachable, - else => unreachable, - } - } - - pub fn copy(old: *Int, comp: *Compilation) !*Int { - old.base.typ.base.ref(); - errdefer old.base.typ.base.deref(comp); - - const new = try comp.gpa().create(Value.Int); - new.* = Value.Int{ - .base = Value{ - .id = .Int, - .typ = old.base.typ, - .ref_count = std.atomic.Int(usize).init(1), - }, - .big_int = undefined, - }; - errdefer comp.gpa().destroy(new); - - new.big_int = try old.big_int.clone(); - errdefer new.big_int.deinit(); - - return new; - } - - pub fn destroy(self: *Int, comp: *Compilation) void { - self.big_int.deinit(); - comp.gpa().destroy(self); - } + pub const Bytes = struct { + base: Value = Value{ .tag = .bytes }, + data: []u8, + }; +}; + +pub const MemoryCell = struct { + parent: Parent, + contents: *Value, + + pub const Parent = union(enum) { + none, + struct_field: struct { + struct_base: *MemoryCell, + field_index: usize, + }, + array_elem: struct { + array_base: *MemoryCell, + elem_index: usize, + }, + union_field: *MemoryCell, + err_union_code: *MemoryCell, + err_union_payload: *MemoryCell, + optional_payload: *MemoryCell, + optional_flag: *MemoryCell, }; }; diff --git a/test/stage2/ir.zig b/test/stage2/ir.zig new file mode 100644 index 000000000..e948e7a8b --- /dev/null +++ b/test/stage2/ir.zig @@ -0,0 +1,37 @@ +test "hello world IR" { + exeCmp( + \\@0 = "Hello, world!\n" + \\ + \\@1 = fn({ + \\ %0 : usize = 1 ;SYS_write + \\ %1 : usize = 1 ;STDOUT_FILENO + \\ %2 = ptrtoint(@0) ; msg ptr + \\ %3 = fieldptr(@0, "len") ; msg len ptr + \\ %4 = deref(%3) ; msg len + \\ %5 = asm("syscall", + \\ volatile=1, + \\ output="={rax}", + \\ inputs=["{rax}", "{rdi}", "{rsi}", "{rdx}"], + \\ clobbers=["rcx", "r11", "memory"], + \\ args=[%0, %1, %2, %4]) + \\ + \\ %6 : usize = 231 ;SYS_exit_group + \\ %7 : usize = 0 ;exit code + \\ %8 = asm("syscall", + \\ volatile=1, + \\ output="={rax}", + \\ inputs=["{rax}", "{rdi}"], + \\ clobbers=["rcx", "r11", "memory"], + \\ args=[%6, %7]) + \\ + \\ %9 = unreachable() + \\}, cc=naked); + \\ + \\@2 = export("_start", @1) + , + \\Hello, world! + \\ + ); +} + +fn exeCmp(src: []const u8, expected_stdout: []const u8) void {}