Commit a46d24af1c

Andrew Kelley <andrew@ziglang.org>
2020-12-31 09:54:02
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.
1 parent 3f7d9b5
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");
src/link/cbe.h
@@ -41,4 +41,4 @@
 #include <stdint.h>
 #define int128_t __int128
 #define uint128_t unsigned __int128
-
+#include <string.h>
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", .{});
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;
     }
 
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);
     }
src/test.zig
@@ -782,6 +782,7 @@ pub const TestContext = struct {
                                 "-std=c89",
                                 "-pedantic",
                                 "-Werror",
+                                "-Wno-declaration-after-statement",
                                 "--",
                                 "-lc",
                                 exe_path,
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,
+        };
     };
 };
 
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
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",
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);
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;