commit 93bb1d93cd18e739410009e7c77048d151a93b10 (tree)
parent 6c2e0c2046a4c1d01587cc15ea2f59af32743eb4
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 31 Dec 2020 16:28:08 -0800
Merge pull request #7616 from ziglang/stage2-inferred-vars
stage2: inferred local variables
Diffstat:
12 files changed, 347 insertions(+), 71 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -41,4 +41,4 @@
#include <stdint.h>
#define int128_t __int128
#define uint128_t unsigned __int128
-
+#include <string.h>
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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 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 analyzeInstAllocInferredMut(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
- return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferredMut", .{});
+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
@@ -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
@@ -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"});
+ }
}