From a46d24af1cf2885991c67bf39a2e639891c16121 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 31 Dec 2020 01:54:02 -0700 Subject: [PATCH] stage2: inferred local variables This patch introduces the following new things: Types: - inferred_alloc - 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. - The payload for this type simply points to the corresponding Value payload. Values: - inferred_alloc - 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. ZIR instructions: - store_to_inferred_ptr, - Same as `store` but the type of the value being stored will be used to infer the pointer type. - resolve_inferred_alloc - 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. Changes to the C backend: * Implements the bitcast instruction. If the source and dest types are both pointers, uses a cast, otherwise uses memcpy. * Tests are run with -Wno-declaration-after-statement. Someday we can conform to this but not today. In ZIR form it looks like this: ```zir fn_body main { // unanalyzed %0 = dbg_stmt() =>%1 = alloc_inferred() %2 = declval_in_module(Decl(add)) %3 = deref(%2) %4 = param_type(%3, 0) %5 = const(TypedValue{ .ty = comptime_int, .val = 1}) %6 = as(%4, %5) %7 = param_type(%3, 1) %8 = const(TypedValue{ .ty = comptime_int, .val = 2}) %9 = as(%7, %8) %10 = call(%3, [%6, %9], modifier=auto) =>%11 = store_to_inferred_ptr(%1, %10) =>%12 = resolve_inferred_alloc(%1) %13 = dbg_stmt() %14 = ret_type() %15 = const(TypedValue{ .ty = comptime_int, .val = 3}) %16 = sub(%10, %15) %17 = as(%14, %16) %18 = return(%17) } // fn_body main ``` I have not played around with very many test cases yet. Some interesting ones that I want to look at before merging: ```zig var x = blk: { var y = foo(); y.a = 1; break :blk y; }; ``` In the above test case, x and y are supposed to alias. ```zig var x = if (bar()) blk: { var y = foo(); y.a = 1; break :blk y; } else blk: { var z = baz(); z.b = 1; break :blk z; }; ``` In the above test case, x, y, and z are supposed to alias. I also haven't tested with `var` instead of `const` yet. --- src/Module.zig | 9 ++- src/astgen.zig | 13 +++- src/codegen/c.zig | 20 ++++++ src/ir.zig | 2 +- src/link/cbe.h | 2 +- src/test.zig | 1 + src/type.zig | 148 ++++++++++++++++++++++++++++---------------- src/value.zig | 36 +++++++++++ src/zir.zig | 12 ++++ src/zir_sema.zig | 61 +++++++++++++++++- test/stage2/cbe.zig | 15 +++++ 11 files changed, 261 insertions(+), 58 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index ca0718c3d5..884b5fb8d4 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); } diff --git a/src/astgen.zig b/src/astgen.zig index c5261c4073..2ce21dbab4 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); + resolve_inferred_alloc = alloc; break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred_mut).? } }; }; 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..6d9563b991 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -275,6 +275,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { try writer.writeAll(" {"); const func: *Module.Fn = func_payload.data; + //func.dump(module.*); const instructions = func.analysis.success.instructions; if (instructions.len > 0) { try writer.writeAll("\n"); @@ -285,6 +286,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).?, "=="), @@ -537,6 +539,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..ea21be0e68 100644 --- a/src/type.zig +++ b/src/type.zig @@ -78,6 +78,7 @@ pub const Type = extern union { .const_slice, .mut_slice, .pointer, + .inferred_alloc, => return .Pointer, .optional, @@ -158,6 +159,8 @@ pub const Type = extern union { .optional_single_mut_pointer, => self.cast(Payload.ElemType), + .inferred_alloc => unreachable, + else => null, }; } @@ -384,6 +387,7 @@ pub const Type = extern union { .enum_literal, .anyerror_void_error_union, .@"anyframe", + .inferred_alloc, => unreachable, .array_u8, @@ -686,6 +690,7 @@ pub const Type = extern union { const name = ty.castTag(.error_set_single).?.data; return out_stream.print("error{{{s}}}", .{name}); }, + .inferred_alloc => return out_stream.writeAll("(inferred allocation type)"), } unreachable; } @@ -733,6 +738,7 @@ 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 => unreachable, else => return Value.Tag.ty.create(allocator, self), } } @@ -803,6 +809,8 @@ pub const Type = extern union { .enum_literal, .empty_struct, => false, + + .inferred_alloc => unreachable, }; } @@ -920,6 +928,7 @@ pub const Type = extern union { .@"undefined", .enum_literal, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -943,6 +952,7 @@ pub const Type = extern union { .enum_literal => unreachable, .single_const_pointer_to_comptime_int => unreachable, .empty_struct => unreachable, + .inferred_alloc => unreachable, .u8, .i8, @@ -1121,6 +1131,7 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .single_const_pointer_to_comptime_int, + .inferred_alloc, => true, .pointer => self.castTag(.pointer).?.data.size == .One, @@ -1203,6 +1214,7 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .single_const_pointer_to_comptime_int, + .inferred_alloc, => .One, .pointer => self.castTag(.pointer).?.data.size, @@ -1273,6 +1285,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .const_slice, @@ -1345,6 +1358,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .single_const_pointer, @@ -1426,6 +1440,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .pointer => { @@ -1502,6 +1517,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .pointer => { @@ -1569,58 +1585,58 @@ 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 => unreachable, .array => self.castTag(.array).?.data.elem_type, .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type, @@ -1742,6 +1758,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, .array => self.castTag(.array).?.data.len, @@ -1808,6 +1825,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, .single_const_pointer, @@ -1891,6 +1909,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .int_signed, @@ -1966,6 +1985,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .int_unsigned, @@ -2031,6 +2051,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, .int_unsigned => .{ @@ -2120,6 +2141,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .usize, @@ -2232,6 +2254,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2310,6 +2333,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, } } @@ -2387,6 +2411,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, } } @@ -2464,6 +2489,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2538,6 +2564,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2612,6 +2639,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2686,6 +2714,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, }; } @@ -2778,6 +2807,7 @@ pub const Type = extern union { ty = ty.castTag(.pointer).?.data.pointee_type; continue; }, + .inferred_alloc => unreachable, }; } @@ -2846,6 +2876,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => return false, .c_const_pointer, @@ -2931,6 +2962,7 @@ pub const Type = extern union { .c_const_pointer, .c_mut_pointer, .pointer, + .inferred_alloc, => unreachable, .empty_struct => self.castTag(.empty_struct).?.data, @@ -3068,6 +3100,10 @@ pub const Type = extern union { error_set, error_set_single, empty_struct, + /// 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, pub const last_no_payload_tag = Tag.const_slice_u8; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -3148,6 +3184,7 @@ pub const Type = extern union { .error_set => Payload.Decl, .error_set_single => Payload.Name, .empty_struct => Payload.ContainerScope, + .inferred_alloc => Payload.InferredAlloc, }; } @@ -3261,6 +3298,13 @@ pub const Type = extern union { base: Payload, data: *Module.Scope.Container, }; + + pub const InferredAlloc = struct { + pub const base_tag = Tag.inferred_alloc; + + base: Payload = .{ .tag = base_tag }, + data: *Value.Payload.InferredAlloc, + }; }; }; 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..b6c17a2195 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"); @@ -55,8 +57,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), @@ -428,13 +432,66 @@ fn analyzeInstAllocMut(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerE } fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); + 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 = try Type.Tag.inferred_alloc.create(scope.arena(), val_payload), + .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 is_mut = true; + const final_ptr_ty = try mod.simplePtrType(scope, inst.base.src, final_elem_ty, is_mut, .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 { const ptr = try resolveInst(mod, scope, inst.positionals.lhs); const value = try resolveInst(mod, scope, inst.positionals.rhs); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 0d2a6d4aec..49659276b3 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -51,6 +51,21 @@ pub fn addCases(ctx: *TestContext) !void { , ""); } + { + var case = ctx.exeFromCompiledC("inferred local const", .{}); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\export fn main() c_int { + \\ const x = add(1, 2); + \\ return x - 3; + \\} + , ""); + } + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable;