diff --git a/src/Module.zig b/src/Module.zig index ca0718c3d5..5ea78d06d1 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3189,7 +3189,14 @@ pub fn floatSub( } } -pub fn simplePtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) Allocator.Error!Type { +pub fn simplePtrType( + self: *Module, + scope: *Scope, + src: usize, + elem_ty: Type, + mutable: bool, + size: std.builtin.TypeInfo.Pointer.Size, +) Allocator.Error!Type { if (!mutable and size == .Slice and elem_ty.eql(Type.initTag(.u8))) { return Type.initTag(.const_slice_u8); } @@ -3414,3 +3421,9 @@ pub fn getTarget(self: Module) Target { pub fn optimizeMode(self: Module) std.builtin.Mode { return self.comp.bin_file.options.optimize_mode; } + +pub fn validateVarType(mod: *Module, scope: *Scope, src: usize, ty: Type) !void { + if (!ty.isValidVarType(false)) { + return mod.fail(scope, src, "variable of type '{}' must be const or comptime", .{ty}); + } +} diff --git a/src/astgen.zig b/src/astgen.zig index c5261c4073..5cf8e12587 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -585,6 +585,7 @@ fn varDecl( switch (tree.token_ids[node.mut_token]) { .Keyword_const => { + var resolve_inferred_alloc: ?*zir.Inst = null; // Depending on the type of AST the initialization expression is, we may need an lvalue // or an rvalue as a result location. If it is an rvalue, we can use the instruction as // the variable, no memory location needed. @@ -595,6 +596,7 @@ fn varDecl( break :r ResultLoc{ .ptr = alloc }; } else { const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred); + resolve_inferred_alloc = &alloc.base; break :r ResultLoc{ .inferred_ptr = alloc }; } } else r: { @@ -604,6 +606,9 @@ fn varDecl( break :r .none; }; const init_inst = try expr(mod, scope, result_loc, init_node); + if (resolve_inferred_alloc) |inst| { + _ = try addZIRUnOp(mod, scope, name_src, .resolve_inferred_alloc, inst); + } const sub_scope = try block_arena.create(Scope.LocalVal); sub_scope.* = .{ .parent = scope, @@ -614,15 +619,20 @@ fn varDecl( return &sub_scope.base; }, .Keyword_var => { + var resolve_inferred_alloc: ?*zir.Inst = null; const var_data: struct { result_loc: ResultLoc, alloc: *zir.Inst } = if (node.getTypeNode()) |type_node| a: { const type_inst = try typeExpr(mod, scope, type_node); const alloc = try addZIRUnOp(mod, scope, name_src, .alloc_mut, type_inst); break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } }; } else a: { - const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred_mut); - break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred_mut).? } }; + const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred_mut); + resolve_inferred_alloc = &alloc.base; + break :a .{ .alloc = &alloc.base, .result_loc = .{ .inferred_ptr = alloc } }; }; const init_inst = try expr(mod, scope, var_data.result_loc, init_node); + if (resolve_inferred_alloc) |inst| { + _ = try addZIRUnOp(mod, scope, name_src, .resolve_inferred_alloc, inst); + } const sub_scope = try block_arena.create(Scope.LocalPtr); sub_scope.* = .{ .parent = scope, @@ -2717,7 +2727,8 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr return mod.fail(scope, result.src, "TODO implement rlWrap .bitcasted_ptr", .{}); }, .inferred_ptr => |alloc| { - return addZIRBinOp(mod, scope, result.src, .store, &alloc.base, result); + _ = try addZIRBinOp(mod, scope, result.src, .store_to_inferred_ptr, &alloc.base, result); + return result; }, .block_ptr => |block_ptr| { return mod.fail(scope, result.src, "TODO implement rlWrap .block_ptr", .{}); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7cd4479bd9..c6c29942d9 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -285,6 +285,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { .arg => try genArg(&ctx), .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), .block => try genBlock(&ctx, file, inst.castTag(.block).?), + .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?), .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), .call => try genCall(&ctx, file, inst.castTag(.call).?), .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), @@ -295,6 +296,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), + .load => try genLoad(&ctx, file, inst.castTag(.load).?), .ret => try genRet(&ctx, file, inst.castTag(.ret).?), .retvoid => try genRetVoid(file), .store => try genStore(&ctx, file, inst.castTag(.store).?), @@ -429,6 +431,16 @@ fn genRetVoid(file: *C) !?[]u8 { return null; } +fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { + const operand = try ctx.resolveInst(inst.operand); + const writer = file.main.writer(); + try indent(file); + const local_name = try ctx.name(); + try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); + try writer.print(" = *{s};\n", .{operand}); + return local_name; +} + fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { try indent(file); const writer = file.main.writer(); @@ -440,7 +452,6 @@ fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { if (inst.base.isUnused()) return null; try indent(file); - const op = inst.operand; const writer = file.main.writer(); const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); @@ -537,6 +548,24 @@ fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 { return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); } +fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { + const writer = file.main.writer(); + try indent(file); + const local_name = try ctx.name(); + const operand = try ctx.resolveInst(inst.operand); + try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); + if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { + try writer.writeAll(" = ("); + try renderType(ctx, writer, inst.base.ty); + try writer.print("){s};\n", .{operand}); + } else { + try writer.writeAll(";\n"); + try indent(file); + try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name }); + } + return local_name; +} + fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { try indent(file); try file.main.writer().writeAll("zig_breakpoint();\n"); diff --git a/src/ir.zig b/src/ir.zig index fc29323247..dda625c735 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -196,7 +196,7 @@ pub const Inst = struct { pub fn value(base: *Inst) ?Value { if (base.ty.onePossibleValue()) |opv| return opv; - const inst = base.cast(Constant) orelse return null; + const inst = base.castTag(.constant) orelse return null; return inst.val; } diff --git a/src/link/cbe.h b/src/link/cbe.h index e62e6766ef..8452af8fbc 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -41,4 +41,4 @@ #include #define int128_t __int128 #define uint128_t unsigned __int128 - +#include diff --git a/src/test.zig b/src/test.zig index b996a25c78..6d76ae39c1 100644 --- a/src/test.zig +++ b/src/test.zig @@ -782,6 +782,7 @@ pub const TestContext = struct { "-std=c89", "-pedantic", "-Werror", + "-Wno-declaration-after-statement", "--", "-lc", exe_path, diff --git a/src/type.zig b/src/type.zig index 9d834a19f2..7d35200727 100644 --- a/src/type.zig +++ b/src/type.zig @@ -78,6 +78,8 @@ pub const Type = extern union { .const_slice, .mut_slice, .pointer, + .inferred_alloc_const, + .inferred_alloc_mut, => return .Pointer, .optional, @@ -158,6 +160,9 @@ pub const Type = extern union { .optional_single_mut_pointer, => self.cast(Payload.ElemType), + .inferred_alloc_const => unreachable, + .inferred_alloc_mut => unreachable, + else => null, }; } @@ -384,6 +389,8 @@ pub const Type = extern union { .enum_literal, .anyerror_void_error_union, .@"anyframe", + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, .array_u8, @@ -686,6 +693,8 @@ pub const Type = extern union { const name = ty.castTag(.error_set_single).?.data; return out_stream.print("error{{{s}}}", .{name}); }, + .inferred_alloc_const => return out_stream.writeAll("(inferred_alloc_const)"), + .inferred_alloc_mut => return out_stream.writeAll("(inferred_alloc_mut)"), } unreachable; } @@ -733,6 +742,8 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type), .const_slice_u8 => return Value.initTag(.const_slice_u8_type), .enum_literal => return Value.initTag(.enum_literal_type), + .inferred_alloc_const => unreachable, + .inferred_alloc_mut => unreachable, else => return Value.Tag.ty.create(allocator, self), } } @@ -803,6 +814,9 @@ pub const Type = extern union { .enum_literal, .empty_struct, => false, + + .inferred_alloc_const => unreachable, + .inferred_alloc_mut => unreachable, }; } @@ -920,6 +934,8 @@ pub const Type = extern union { .@"undefined", .enum_literal, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, }; } @@ -943,6 +959,8 @@ pub const Type = extern union { .enum_literal => unreachable, .single_const_pointer_to_comptime_int => unreachable, .empty_struct => unreachable, + .inferred_alloc_const => unreachable, + .inferred_alloc_mut => unreachable, .u8, .i8, @@ -1121,6 +1139,8 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .single_const_pointer_to_comptime_int, + .inferred_alloc_const, + .inferred_alloc_mut, => true, .pointer => self.castTag(.pointer).?.data.size == .One, @@ -1203,6 +1223,8 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .single_const_pointer_to_comptime_int, + .inferred_alloc_const, + .inferred_alloc_mut, => .One, .pointer => self.castTag(.pointer).?.data.size, @@ -1273,6 +1295,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, .const_slice, @@ -1345,6 +1369,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, .single_const_pointer, @@ -1426,6 +1452,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, .pointer => { @@ -1502,6 +1530,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, .pointer => { @@ -1569,58 +1599,59 @@ pub const Type = extern union { /// Asserts the type is a pointer or array type. pub fn elemType(self: Type) Type { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .optional, - .optional_single_const_pointer, - .optional_single_mut_pointer, - .enum_literal, - .error_union, - .@"anyframe", - .anyframe_T, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .empty_struct, - => unreachable, + .u8 => unreachable, + .i8 => unreachable, + .u16 => unreachable, + .i16 => unreachable, + .u32 => unreachable, + .i32 => unreachable, + .u64 => unreachable, + .i64 => unreachable, + .usize => unreachable, + .isize => unreachable, + .c_short => unreachable, + .c_ushort => unreachable, + .c_int => unreachable, + .c_uint => unreachable, + .c_long => unreachable, + .c_ulong => unreachable, + .c_longlong => unreachable, + .c_ulonglong => unreachable, + .c_longdouble => unreachable, + .f16 => unreachable, + .f32 => unreachable, + .f64 => unreachable, + .f128 => unreachable, + .c_void => unreachable, + .bool => unreachable, + .void => unreachable, + .type => unreachable, + .anyerror => unreachable, + .comptime_int => unreachable, + .comptime_float => unreachable, + .noreturn => unreachable, + .@"null" => unreachable, + .@"undefined" => unreachable, + .fn_noreturn_no_args => unreachable, + .fn_void_no_args => unreachable, + .fn_naked_noreturn_no_args => unreachable, + .fn_ccc_void_no_args => unreachable, + .function => unreachable, + .int_unsigned => unreachable, + .int_signed => unreachable, + .optional => unreachable, + .optional_single_const_pointer => unreachable, + .optional_single_mut_pointer => unreachable, + .enum_literal => unreachable, + .error_union => unreachable, + .@"anyframe" => unreachable, + .anyframe_T => unreachable, + .anyerror_void_error_union => unreachable, + .error_set => unreachable, + .error_set_single => unreachable, + .empty_struct => unreachable, + .inferred_alloc_const => unreachable, + .inferred_alloc_mut => unreachable, .array => self.castTag(.array).?.data.elem_type, .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type, @@ -1742,6 +1773,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, .array => self.castTag(.array).?.data.len, @@ -1808,6 +1841,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, .single_const_pointer, @@ -1891,6 +1926,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, .int_signed, @@ -1966,6 +2003,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, .int_unsigned, @@ -2031,6 +2070,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, .int_unsigned => .{ @@ -2120,6 +2161,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, .usize, @@ -2232,6 +2275,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, }; } @@ -2310,6 +2355,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, } } @@ -2387,6 +2434,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, } } @@ -2464,6 +2513,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, }; } @@ -2538,6 +2589,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, }; } @@ -2612,6 +2665,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, }; } @@ -2686,6 +2741,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => false, }; } @@ -2778,6 +2835,8 @@ pub const Type = extern union { ty = ty.castTag(.pointer).?.data.pointee_type; continue; }, + .inferred_alloc_const => unreachable, + .inferred_alloc_mut => unreachable, }; } @@ -2846,6 +2905,8 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc_const, + .inferred_alloc_mut, => return false, .c_const_pointer, @@ -2931,6 +2992,8 @@ pub const Type = extern union { .c_const_pointer, .c_mut_pointer, .pointer, + .inferred_alloc_const, + .inferred_alloc_mut, => unreachable, .empty_struct => self.castTag(.empty_struct).?.data, @@ -3041,7 +3104,13 @@ pub const Type = extern union { single_const_pointer_to_comptime_int, anyerror_void_error_union, @"anyframe", - const_slice_u8, // See last_no_payload_tag below. + const_slice_u8, + /// This is a special value that tracks a set of types that have been stored + /// to an inferred allocation. It does not support most of the normal type queries. + /// However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. + inferred_alloc_mut, + /// Same as `inferred_alloc_mut` but the local is `var` not `const`. + inferred_alloc_const, // See last_no_payload_tag below. // After this, the tag requires a payload. array_u8, @@ -3069,7 +3138,7 @@ pub const Type = extern union { error_set_single, empty_struct, - pub const last_no_payload_tag = Tag.const_slice_u8; + pub const last_no_payload_tag = Tag.inferred_alloc_const; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; pub fn Type(comptime t: Tag) type { @@ -3116,6 +3185,8 @@ pub const Type = extern union { .anyerror_void_error_union, .@"anyframe", .const_slice_u8, + .inferred_alloc_const, + .inferred_alloc_mut, => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), .array_u8, diff --git a/src/value.zig b/src/value.zig index 91b21511d4..4450099069 100644 --- a/src/value.zig +++ b/src/value.zig @@ -7,6 +7,7 @@ const BigIntMutable = std.math.big.int.Mutable; const Target = std.Target; const Allocator = std.mem.Allocator; const Module = @import("Module.zig"); +const ir = @import("ir.zig"); /// This is the raw data, with no bookkeeping, no memory awareness, /// no de-duplication, and no type system awareness. @@ -101,6 +102,9 @@ pub const Value = extern union { enum_literal, error_set, @"error", + /// This is a special value that tracks a set of types that have been stored + /// to an inferred allocation. It does not support any of the normal value queries. + inferred_alloc, pub const last_no_payload_tag = Tag.bool_false; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -189,6 +193,7 @@ pub const Value = extern union { .float_128 => Payload.Float_128, .error_set => Payload.ErrorSet, .@"error" => Payload.Error, + .inferred_alloc => Payload.InferredAlloc, }; } @@ -383,6 +388,8 @@ pub const Value = extern union { // memory is managed by the declaration .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet), + + .inferred_alloc => unreachable, } } @@ -501,6 +508,7 @@ pub const Value = extern union { return out_stream.writeAll("}"); }, .@"error" => return out_stream.print("error.{}", .{val.castTag(.@"error").?.data.name}), + .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), }; } @@ -613,6 +621,7 @@ pub const Value = extern union { .enum_literal, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, }; } @@ -683,6 +692,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .undef => unreachable, @@ -768,6 +778,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .undef => unreachable, @@ -853,6 +864,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .undef => unreachable, @@ -966,6 +978,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1055,6 +1068,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1213,6 +1227,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1289,6 +1304,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1525,6 +1541,8 @@ pub const Value = extern union { hasher.update(payload.name); std.hash.autoHash(&hasher, payload.value); }, + + .inferred_alloc => unreachable, } return hasher.final(); } @@ -1602,6 +1620,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .ref_val => self.castTag(.ref_val).?.data, @@ -1687,6 +1706,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .empty_array => unreachable, // out of bounds array index @@ -1793,6 +1813,7 @@ pub const Value = extern union { .undef => unreachable, .unreachable_value => unreachable, + .inferred_alloc => unreachable, .null_value => true, }; } @@ -1801,6 +1822,7 @@ pub const Value = extern union { pub fn isFloat(self: Value) bool { return switch (self.tag()) { .undef => unreachable, + .inferred_alloc => unreachable, .float_16, .float_32, @@ -1890,6 +1912,7 @@ pub const Value = extern union { .undef => unreachable, .unreachable_value => unreachable, + .inferred_alloc => unreachable, }; } @@ -2020,6 +2043,19 @@ pub const Value = extern union { value: u16, }, }; + + pub const InferredAlloc = struct { + pub const base_tag = Tag.inferred_alloc; + + base: Payload = .{ .tag = base_tag }, + data: struct { + /// The value stored in the inferred allocation. This will go into + /// peer type resolution. This is stored in a separate list so that + /// the items are contiguous in memory and thus can be passed to + /// `Module.resolvePeerTypes`. + stored_inst_list: std.ArrayListUnmanaged(*ir.Inst) = .{}, + }, + }; }; /// Big enough to fit any non-BigInt value diff --git a/src/zir.zig b/src/zir.zig index 21bd4f8435..ce8498d7e8 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -241,12 +241,20 @@ pub const Inst = struct { const_slice_type, /// Create a pointer type with attributes ptr_type, + /// Each `store_to_inferred_ptr` puts the type of the stored value into a set, + /// and then `resolve_inferred_alloc` triggers peer type resolution on the set. + /// The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which + /// is the allocation that needs to have its type inferred. + resolve_inferred_alloc, /// Slice operation `array_ptr[start..end:sentinel]` slice, /// Slice operation with just start `lhs[rhs..]` slice_start, /// Write a value to a pointer. For loading, see `deref`. store, + /// Same as `store` but the type of the value being stored will be used to infer + /// the pointer type. + store_to_inferred_ptr, /// String Literal. Makes an anonymous Decl and then takes a pointer to it. str, /// Arithmetic subtraction. Asserts no integer overflow. @@ -319,6 +327,7 @@ pub const Inst = struct { .ref, .bitcast_ref, .typeof, + .resolve_inferred_alloc, .single_const_ptr_type, .single_mut_ptr_type, .many_const_ptr_type, @@ -355,6 +364,7 @@ pub const Inst = struct { .shl, .shr, .store, + .store_to_inferred_ptr, .sub, .subwrap, .cmp_lt, @@ -498,6 +508,7 @@ pub const Inst = struct { .mut_slice_type, .const_slice_type, .store, + .store_to_inferred_ptr, .str, .sub, .subwrap, @@ -522,6 +533,7 @@ pub const Inst = struct { .import, .switch_range, .typeof_peer, + .resolve_inferred_alloc, => false, .@"break", diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 2e3cced839..629dfa0c9a 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -10,10 +10,12 @@ const std = @import("std"); const mem = std.mem; const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const log = std.log.scoped(.sema); + const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const TypedValue = @import("TypedValue.zig"); -const assert = std.debug.assert; const ir = @import("ir.zig"); const zir = @import("zir.zig"); const Module = @import("Module.zig"); @@ -28,8 +30,18 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! switch (old_inst.tag) { .alloc => return analyzeInstAlloc(mod, scope, old_inst.castTag(.alloc).?), .alloc_mut => return analyzeInstAllocMut(mod, scope, old_inst.castTag(.alloc_mut).?), - .alloc_inferred => return analyzeInstAllocInferred(mod, scope, old_inst.castTag(.alloc_inferred).?), - .alloc_inferred_mut => return analyzeInstAllocInferredMut(mod, scope, old_inst.castTag(.alloc_inferred_mut).?), + .alloc_inferred => return analyzeInstAllocInferred( + mod, + scope, + old_inst.castTag(.alloc_inferred).?, + .inferred_alloc_const, + ), + .alloc_inferred_mut => return analyzeInstAllocInferred( + mod, + scope, + old_inst.castTag(.alloc_inferred_mut).?, + .inferred_alloc_mut, + ), .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?), .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?), .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?), @@ -55,8 +67,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?), .ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?), .ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?), + .resolve_inferred_alloc => return analyzeInstResolveInferredAlloc(mod, scope, old_inst.castTag(.resolve_inferred_alloc).?), .ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?), .ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?), + .store_to_inferred_ptr => return analyzeInstStoreToInferredPtr(mod, scope, old_inst.castTag(.store_to_inferred_ptr).?), .single_const_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?, false, .One), .single_mut_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?, true, .One), .many_const_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.many_const_ptr_type).?, false, .Many), @@ -419,20 +433,83 @@ fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErro fn analyzeInstAllocMut(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const var_type = try resolveType(mod, scope, inst.positionals.operand); - if (!var_type.isValidVarType(false)) { - return mod.fail(scope, inst.base.src, "variable of type '{}' must be const or comptime", .{var_type}); - } + try mod.validateVarType(scope, inst.base.src, var_type); const ptr_type = try mod.simplePtrType(scope, inst.base.src, var_type, true, .One); const b = try mod.requireRuntimeBlock(scope, inst.base.src); return mod.addNoOp(b, inst.base.src, ptr_type, .alloc); } -fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); +fn analyzeInstAllocInferred( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.NoOp, + mut_tag: Type.Tag, +) InnerError!*Inst { + const val_payload = try scope.arena().create(Value.Payload.InferredAlloc); + val_payload.* = .{ + .data = .{}, + }; + // `Module.constInst` does not add the instruction to the block because it is + // not needed in the case of constant values. However here, we plan to "downgrade" + // to a normal instruction when we hit `resolve_inferred_alloc`. So we append + // to the block even though it is currently a `.constant`. + const result = try mod.constInst(scope, inst.base.src, .{ + .ty = switch (mut_tag) { + .inferred_alloc_const => Type.initTag(.inferred_alloc_const), + .inferred_alloc_mut => Type.initTag(.inferred_alloc_mut), + else => unreachable, + }, + .val = Value.initPayload(&val_payload.base), + }); + const block = try mod.requireFunctionBlock(scope, inst.base.src); + try block.instructions.append(mod.gpa, result); + return result; } -fn analyzeInstAllocInferredMut(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferredMut", .{}); +fn analyzeInstResolveInferredAlloc( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.UnOp, +) InnerError!*Inst { + const ptr = try resolveInst(mod, scope, inst.positionals.operand); + const ptr_val = ptr.castTag(.constant).?.val; + const inferred_alloc = ptr_val.castTag(.inferred_alloc).?; + const peer_inst_list = inferred_alloc.data.stored_inst_list.items; + const final_elem_ty = try mod.resolvePeerTypes(scope, peer_inst_list); + const var_is_mut = switch (ptr.ty.tag()) { + .inferred_alloc_const => false, + .inferred_alloc_mut => true, + else => unreachable, + }; + if (var_is_mut) { + try mod.validateVarType(scope, inst.base.src, final_elem_ty); + } + const final_ptr_ty = try mod.simplePtrType(scope, inst.base.src, final_elem_ty, true, .One); + + // Change it to a normal alloc. + ptr.ty = final_ptr_ty; + ptr.tag = .alloc; + + return mod.constVoid(scope, inst.base.src); +} + +fn analyzeInstStoreToInferredPtr( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.BinOp, +) InnerError!*Inst { + const ptr = try resolveInst(mod, scope, inst.positionals.lhs); + const value = try resolveInst(mod, scope, inst.positionals.rhs); + const inferred_alloc = ptr.castTag(.constant).?.val.castTag(.inferred_alloc).?; + // Add the stored instruction to the set we will use to resolve peer types + // for the inferred allocation. + try inferred_alloc.data.stored_inst_list.append(scope.arena(), value); + // Create a new alloc with exactly the type the pointer wants. + // Later it gets cleaned up by aliasing the alloc we are supposed to be storing to. + const ptr_ty = try mod.simplePtrType(scope, inst.base.src, value.ty, true, .One); + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const bitcasted_ptr = try mod.addUnOp(b, inst.base.src, ptr_ty, .bitcast, ptr); + return mod.storePtr(scope, inst.base.src, bitcasted_ptr, value); } fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 0d2a6d4aec..a0a4587983 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -51,6 +51,23 @@ pub fn addCases(ctx: *TestContext) !void { , ""); } + { + var case = ctx.exeFromCompiledC("inferred local const and var", .{}); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\export fn main() c_int { + \\ const x = add(1, 2); + \\ var y = add(3, 0); + \\ y -= x; + \\ return y; + \\} + , ""); + } + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 5a200e9cf4..93a8037064 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1322,4 +1322,13 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{":2:5: error: unused for label"}); } + + { + var case = ctx.exe("bad inferred variable type", linux_x64); + case.addError( + \\export fn foo() void { + \\ var x = null; + \\} + , &[_][]const u8{":2:9: error: variable of type '@Type(.Null)' must be const or comptime"}); + } }