authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2020-10-17 01:09:42+03:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2020-10-30 15:58:13+02:00
log12e4c648ccc68f5190dd5076088b3959ebeee65d
treee045157d9fba4648bb450510a4762f4937799308
parent4155d2ae242d18c0bc280aa22f733bf7dcb6e1f0
signaturelock-open Commit is signed but in an unrecognized format.

stage2: implement switch validation for integers


3 files changed, 196 insertions(+), 2 deletions(-)

src/RangeSet.zig created+76
...@@ -0,0 +1,76 @@
1const std = @import("std");
2const Order = std.math.Order;
3const Value = @import("value.zig").Value;
4const RangeSet = @This();
5
6ranges: std.ArrayList(Range),
7
8pub const Range = struct {
9 start: Value,
10 end: Value,
11 src: usize,
12};
13
14pub fn init(allocator: *std.mem.Allocator) RangeSet {
15 return .{
16 .ranges = std.ArrayList(Range).init(allocator),
17 };
18}
19
20pub fn deinit(self: *RangeSet) void {
21 self.ranges.deinit();
22}
23
24pub fn add(self: *RangeSet, start: Value, end: Value, src: usize) !?usize {
25 for (self.ranges.items) |range| {
26 if ((start.compare(.gte, range.start) and start.compare(.lte, range.end)) or
27 (end.compare(.gte, range.start) and end.compare(.lte, range.end)))
28 {
29 // ranges overlap
30 return range.src;
31 }
32 }
33 try self.ranges.append(.{
34 .start = start,
35 .end = end,
36 .src = src,
37 });
38 return null;
39}
40
41/// Assumes a and b do not overlap
42fn lessThan(_: void, a: Range, b: Range) bool {
43 return a.start.compare(.lt, b.start);
44}
45
46pub fn spans(self: *RangeSet, start: Value, end: Value) !bool {
47 std.sort.sort(Range, self.ranges.items, {}, lessThan);
48
49 if (!self.ranges.items[0].start.eql(start) or
50 !self.ranges.items[self.ranges.items.len - 1].end.eql(end))
51 {
52 return false;
53 }
54
55 var space: Value.BigIntSpace = undefined;
56
57 var counter = try std.math.big.int.Managed.init(self.ranges.allocator);
58 defer counter.deinit();
59
60 // look for gaps
61 for (self.ranges.items[1..]) |cur, i| {
62 // i starts counting from the second item.
63 const prev = self.ranges.items[i];
64
65 // prev.end + 1 == cur.start
66 try counter.copy(prev.end.toBigInt(&space));
67 try counter.addScalar(counter.toConst(), 1);
68
69 const cur_start_int = cur.start.toBigInt(&space);
70 if (!cur_start_int.eq(counter.toConst())) {
71 return false;
72 }
73 }
74
75 return true;
76}
src/type.zig+72
...@@ -2863,6 +2863,78 @@ pub const Type = extern union {...@@ -2863,6 +2863,78 @@ pub const Type = extern union {
2863 };2863 };
2864 }2864 }
28652865
2866 /// Asserts that self.zigTypeTag() == .Int.
2867 pub fn minInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value {
2868 assert(self.zigTypeTag() == .Int);
2869 const info = self.intInfo(target);
2870
2871 if (!info.signed) {
2872 return Value.initTag(.zero);
2873 }
2874
2875 if ((info.bits - 1) <= std.math.maxInt(u6)) {
2876 const payload = try arena.allocator.create(Value.Payload.Int_i64);
2877 payload.* = .{
2878 .int = -(@as(i64, 1) << @truncate(u6, info.bits - 1)),
2879 };
2880 return Value.initPayload(&payload.base);
2881 }
2882
2883 var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
2884 try res.shiftLeft(res, info.bits - 1);
2885 res.negate();
2886
2887 const res_const = res.toConst();
2888 if (res_const.positive) {
2889 const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
2890 val_payload.* = .{ .limbs = res_const.limbs };
2891 return Value.initPayload(&val_payload.base);
2892 } else {
2893 const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
2894 val_payload.* = .{ .limbs = res_const.limbs };
2895 return Value.initPayload(&val_payload.base);
2896 }
2897 }
2898
2899 /// Asserts that self.zigTypeTag() == .Int.
2900 pub fn maxInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value {
2901 assert(self.zigTypeTag() == .Int);
2902 const info = self.intInfo(target);
2903
2904 if (info.signed and (info.bits - 1) <= std.math.maxInt(u6)) {
2905 const payload = try arena.allocator.create(Value.Payload.Int_i64);
2906 payload.* = .{
2907 .int = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1,
2908 };
2909 return Value.initPayload(&payload.base);
2910 } else if (!info.signed and info.bits <= std.math.maxInt(u6)) {
2911 const payload = try arena.allocator.create(Value.Payload.Int_u64);
2912 payload.* = .{
2913 .int = (@as(u64, 1) << @truncate(u6, info.bits)) - 1,
2914 };
2915 return Value.initPayload(&payload.base);
2916 }
2917
2918 var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1);
2919 try res.shiftLeft(res, info.bits - @boolToInt(info.signed));
2920 const one = std.math.big.int.Const{
2921 .limbs = &[_]std.math.big.Limb{1},
2922 .positive = true,
2923 };
2924 res.sub(res.toConst(), one) catch unreachable;
2925
2926 const res_const = res.toConst();
2927 if (res_const.positive) {
2928 const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive);
2929 val_payload.* = .{ .limbs = res_const.limbs };
2930 return Value.initPayload(&val_payload.base);
2931 } else {
2932 const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative);
2933 val_payload.* = .{ .limbs = res_const.limbs };
2934 return Value.initPayload(&val_payload.base);
2935 }
2936 }
2937
2866 /// This enum does not directly correspond to `std.builtin.TypeId` because2938 /// This enum does not directly correspond to `std.builtin.TypeId` because
2867 /// it has extra enum tags in it, as a way of using less memory. For example,2939 /// it has extra enum tags in it, as a way of using less memory. For example,
2868 /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types2940 /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
src/zir_sema.zig+48-2
...@@ -1268,7 +1268,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In...@@ -1268,7 +1268,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In
1268 .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },1268 .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) },
1269 };1269 };
1270 }1270 }
1271 1271
1272 return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases);1272 return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases);
1273}1273}
12741274
...@@ -1292,10 +1292,56 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw...@@ -1292,10 +1292,56 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw
12921292
1293 // validate for duplicate items/missing else prong1293 // validate for duplicate items/missing else prong
1294 switch (target.ty.zigTypeTag()) {1294 switch (target.ty.zigTypeTag()) {
1295 .Int, .ComptimeInt => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Int, .ComptimeInt", .{}),
1296 .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),1295 .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}),
1297 .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),1296 .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
1298 .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),1297 .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}),
1298 .Int, .ComptimeInt => {
1299 var range_set = @import("RangeSet.zig").init(mod.gpa);
1300 defer range_set.deinit();
1301
1302 for (inst.positionals.items) |item| {
1303 const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
1304 const start_resolved = try resolveInst(mod, scope, range.positionals.lhs);
1305 const start_casted = try mod.coerce(scope, target.ty, start_resolved);
1306 const end_resolved = try resolveInst(mod, scope, range.positionals.rhs);
1307 const end_casted = try mod.coerce(scope, target.ty, end_resolved);
1308
1309 break :blk try range_set.add(
1310 try mod.resolveConstValue(scope, start_casted),
1311 try mod.resolveConstValue(scope, end_casted),
1312 item.src,
1313 );
1314 } else blk: {
1315 const resolved = try resolveInst(mod, scope, item);
1316 const casted = try mod.coerce(scope, target.ty, resolved);
1317 const value = try mod.resolveConstValue(scope, casted);
1318 break :blk try range_set.add(value, value, item.src);
1319 };
1320
1321 if (maybe_src) |previous_src| {
1322 return mod.fail(scope, item.src, "duplicate switch value", .{});
1323 // TODO notes "previous value is here" previous_src
1324 }
1325 }
1326
1327 if (target.ty.zigTypeTag() == .Int) {
1328 var arena = std.heap.ArenaAllocator.init(mod.gpa);
1329 defer arena.deinit();
1330
1331 const start = try target.ty.minInt(&arena, mod.getTarget());
1332 const end = try target.ty.maxInt(&arena, mod.getTarget());
1333 if (try range_set.spans(start, end)) {
1334 if (inst.kw_args.special_prong == .@"else") {
1335 return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{});
1336 }
1337 return;
1338 }
1339 }
1340
1341 if (inst.kw_args.special_prong != .@"else") {
1342 return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{});
1343 }
1344 },
1299 .Bool => {1345 .Bool => {
1300 var true_count: u8 = 0;1346 var true_count: u8 = 0;
1301 var false_count: u8 = 0;1347 var false_count: u8 = 0;