Merge pull request #7616 from ziglang/stage2-inferred-vars

stage2: inferred local variables
This commit is contained in:
Andrew Kelley 2020-12-31 16:28:08 -08:00 committed by GitHub
commit 93bb1d93cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 347 additions and 71 deletions

View File

@ -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});
}
}

View File

@ -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", .{});

View File

@ -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");

View File

@ -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;
}

View File

@ -41,4 +41,4 @@
#include <stdint.h>
#define int128_t __int128
#define uint128_t unsigned __int128
#include <string.h>

View File

@ -782,6 +782,7 @@ pub const TestContext = struct {
"-std=c89",
"-pedantic",
"-Werror",
"-Wno-declaration-after-statement",
"--",
"-lc",
exe_path,

View File

@ -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,

View File

@ -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

View File

@ -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",

View File

@ -10,10 +10,12 @@
const std = @import("std");
const mem = std.mem;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const log = std.log.scoped(.sema);
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const TypedValue = @import("TypedValue.zig");
const assert = std.debug.assert;
const ir = @import("ir.zig");
const zir = @import("zir.zig");
const Module = @import("Module.zig");
@ -28,8 +30,18 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
switch (old_inst.tag) {
.alloc => return analyzeInstAlloc(mod, scope, old_inst.castTag(.alloc).?),
.alloc_mut => return analyzeInstAllocMut(mod, scope, old_inst.castTag(.alloc_mut).?),
.alloc_inferred => return analyzeInstAllocInferred(mod, scope, old_inst.castTag(.alloc_inferred).?),
.alloc_inferred_mut => return analyzeInstAllocInferredMut(mod, scope, old_inst.castTag(.alloc_inferred_mut).?),
.alloc_inferred => return analyzeInstAllocInferred(
mod,
scope,
old_inst.castTag(.alloc_inferred).?,
.inferred_alloc_const,
),
.alloc_inferred_mut => return analyzeInstAllocInferred(
mod,
scope,
old_inst.castTag(.alloc_inferred_mut).?,
.inferred_alloc_mut,
),
.arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?),
.bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?),
.bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?),
@ -55,8 +67,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
.ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?),
.ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?),
.ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?),
.resolve_inferred_alloc => return analyzeInstResolveInferredAlloc(mod, scope, old_inst.castTag(.resolve_inferred_alloc).?),
.ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?),
.ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?),
.store_to_inferred_ptr => return analyzeInstStoreToInferredPtr(mod, scope, old_inst.castTag(.store_to_inferred_ptr).?),
.single_const_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?, false, .One),
.single_mut_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?, true, .One),
.many_const_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.many_const_ptr_type).?, false, .Many),
@ -419,20 +433,83 @@ fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErro
fn analyzeInstAllocMut(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
const var_type = try resolveType(mod, scope, inst.positionals.operand);
if (!var_type.isValidVarType(false)) {
return mod.fail(scope, inst.base.src, "variable of type '{}' must be const or comptime", .{var_type});
}
try mod.validateVarType(scope, inst.base.src, var_type);
const ptr_type = try mod.simplePtrType(scope, inst.base.src, var_type, true, .One);
const b = try mod.requireRuntimeBlock(scope, inst.base.src);
return mod.addNoOp(b, inst.base.src, ptr_type, .alloc);
}
fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{});
fn analyzeInstAllocInferred(
mod: *Module,
scope: *Scope,
inst: *zir.Inst.NoOp,
mut_tag: Type.Tag,
) InnerError!*Inst {
const val_payload = try scope.arena().create(Value.Payload.InferredAlloc);
val_payload.* = .{
.data = .{},
};
// `Module.constInst` does not add the instruction to the block because it is
// not needed in the case of constant values. However here, we plan to "downgrade"
// to a normal instruction when we hit `resolve_inferred_alloc`. So we append
// to the block even though it is currently a `.constant`.
const result = try mod.constInst(scope, inst.base.src, .{
.ty = switch (mut_tag) {
.inferred_alloc_const => Type.initTag(.inferred_alloc_const),
.inferred_alloc_mut => Type.initTag(.inferred_alloc_mut),
else => unreachable,
},
.val = Value.initPayload(&val_payload.base),
});
const block = try mod.requireFunctionBlock(scope, inst.base.src);
try block.instructions.append(mod.gpa, result);
return result;
}
fn analyzeInstAllocInferredMut(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferredMut", .{});
fn analyzeInstResolveInferredAlloc(
mod: *Module,
scope: *Scope,
inst: *zir.Inst.UnOp,
) InnerError!*Inst {
const ptr = try resolveInst(mod, scope, inst.positionals.operand);
const ptr_val = ptr.castTag(.constant).?.val;
const inferred_alloc = ptr_val.castTag(.inferred_alloc).?;
const peer_inst_list = inferred_alloc.data.stored_inst_list.items;
const final_elem_ty = try mod.resolvePeerTypes(scope, peer_inst_list);
const var_is_mut = switch (ptr.ty.tag()) {
.inferred_alloc_const => false,
.inferred_alloc_mut => true,
else => unreachable,
};
if (var_is_mut) {
try mod.validateVarType(scope, inst.base.src, final_elem_ty);
}
const final_ptr_ty = try mod.simplePtrType(scope, inst.base.src, final_elem_ty, true, .One);
// Change it to a normal alloc.
ptr.ty = final_ptr_ty;
ptr.tag = .alloc;
return mod.constVoid(scope, inst.base.src);
}
fn analyzeInstStoreToInferredPtr(
mod: *Module,
scope: *Scope,
inst: *zir.Inst.BinOp,
) InnerError!*Inst {
const ptr = try resolveInst(mod, scope, inst.positionals.lhs);
const value = try resolveInst(mod, scope, inst.positionals.rhs);
const inferred_alloc = ptr.castTag(.constant).?.val.castTag(.inferred_alloc).?;
// Add the stored instruction to the set we will use to resolve peer types
// for the inferred allocation.
try inferred_alloc.data.stored_inst_list.append(scope.arena(), value);
// Create a new alloc with exactly the type the pointer wants.
// Later it gets cleaned up by aliasing the alloc we are supposed to be storing to.
const ptr_ty = try mod.simplePtrType(scope, inst.base.src, value.ty, true, .One);
const b = try mod.requireRuntimeBlock(scope, inst.base.src);
const bitcasted_ptr = try mod.addUnOp(b, inst.base.src, ptr_ty, .bitcast, ptr);
return mod.storePtr(scope, inst.base.src, bitcasted_ptr, value);
}
fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {

View File

@ -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;

View File

@ -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"});
}
}