aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Air.zig5
-rw-r--r--src/AstGen.zig9
-rw-r--r--src/Compilation.zig17
-rw-r--r--src/Module.zig30
-rw-r--r--src/Sema.zig1168
-rw-r--r--src/Zir.zig13
-rw-r--r--src/main.zig6
-rw-r--r--src/print_zir.zig15
-rw-r--r--src/stage1/parser.hpp2
-rw-r--r--src/type.zig16
10 files changed, 885 insertions, 396 deletions
diff --git a/src/Air.zig b/src/Air.zig
index 2c0c38a2ef..302822fc99 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -111,8 +111,9 @@ pub const Inst = struct {
div_floor,
/// Same as `div_floor` with optimized float mode.
div_floor_optimized,
- /// Integer or float division. Guaranteed no remainder.
- /// For integers, wrapping is undefined behavior.
+ /// Integer or float division.
+ /// If a remainder would be produced, undefined behavior occurs.
+ /// For integers, overflow is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 528ef930e6..b6a7450f3a 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -1349,7 +1349,10 @@ fn arrayInitExpr(
}
}
const array_type_inst = try typeExpr(gz, scope, array_init.ast.type_expr);
- _ = try gz.addUnNode(.validate_array_init_ty, array_type_inst, array_init.ast.type_expr);
+ _ = try gz.addPlNode(.validate_array_init_ty, node, Zir.Inst.ArrayInit{
+ .ty = array_type_inst,
+ .init_count = @intCast(u32, array_init.ast.elements.len),
+ });
break :inst .{
.array = array_type_inst,
.elem = .none,
@@ -1940,6 +1943,9 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
.break_inline
else
.@"break";
+ if (break_tag == .break_inline) {
+ _ = try parent_gz.addNode(.check_comptime_control_flow, node);
+ }
_ = try parent_gz.addBreak(break_tag, continue_block, .void_value);
return Zir.Inst.Ref.unreachable_value;
},
@@ -2473,6 +2479,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.repeat_inline,
.panic,
.panic_comptime,
+ .check_comptime_control_flow,
=> {
noreturn_src_node = statement;
break :b true;
diff --git a/src/Compilation.zig b/src/Compilation.zig
index 869cd43f0f..17ffe356a3 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -1494,31 +1494,14 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
);
errdefer test_pkg.destroy(gpa);
- try test_pkg.add(gpa, "builtin", builtin_pkg);
- try test_pkg.add(gpa, "root", test_pkg);
- try test_pkg.add(gpa, "std", std_pkg);
-
break :root_pkg test_pkg;
} else main_pkg;
errdefer if (options.is_test) root_pkg.destroy(gpa);
- var other_pkg_iter = main_pkg.table.valueIterator();
- while (other_pkg_iter.next()) |pkg| {
- try pkg.*.add(gpa, "builtin", builtin_pkg);
- try pkg.*.add(gpa, "std", std_pkg);
- }
-
try main_pkg.addAndAdopt(gpa, "builtin", builtin_pkg);
try main_pkg.add(gpa, "root", root_pkg);
try main_pkg.addAndAdopt(gpa, "std", std_pkg);
- try std_pkg.add(gpa, "builtin", builtin_pkg);
- try std_pkg.add(gpa, "root", root_pkg);
- try std_pkg.add(gpa, "std", std_pkg);
-
- try builtin_pkg.add(gpa, "std", std_pkg);
- try builtin_pkg.add(gpa, "builtin", builtin_pkg);
-
const main_pkg_in_std = m: {
const std_path = try std.fs.path.resolve(arena, &[_][]const u8{
std_pkg.root_src_directory.path orelse ".",
diff --git a/src/Module.zig b/src/Module.zig
index 4576538a35..4ac2775515 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -2283,6 +2283,8 @@ pub const SrcLoc = struct {
.@"while" => tree.whileFull(node).ast.cond_expr,
.for_simple => tree.forSimple(node).ast.cond_expr,
.@"for" => tree.forFull(node).ast.cond_expr,
+ .@"orelse" => node,
+ .@"catch" => node,
else => unreachable,
};
return nodeToSpan(tree, src_node);
@@ -2726,6 +2728,21 @@ pub const SrcLoc = struct {
};
return nodeToSpan(tree, full.ast.value_expr);
},
+ .node_offset_init_ty => |node_off| {
+ const tree = try src_loc.file_scope.getTree(gpa);
+ const node_tags = tree.nodes.items(.tag);
+ const parent_node = src_loc.declRelativeToNodeIndex(node_off);
+
+ var buf: [2]Ast.Node.Index = undefined;
+ const full: Ast.full.ArrayInit = switch (node_tags[parent_node]) {
+ .array_init_one, .array_init_one_comma => tree.arrayInitOne(buf[0..1], parent_node),
+ .array_init_dot_two, .array_init_dot_two_comma => tree.arrayInitDotTwo(&buf, parent_node),
+ .array_init_dot, .array_init_dot_comma => tree.arrayInitDot(parent_node),
+ .array_init, .array_init_comma => tree.arrayInit(parent_node),
+ else => unreachable,
+ };
+ return nodeToSpan(tree, full.ast.type_expr);
+ },
}
}
@@ -3046,6 +3063,9 @@ pub const LazySrcLoc = union(enum) {
/// The source location points to the default value of a field.
/// The Decl is determined contextually.
node_offset_field_default: i32,
+ /// The source location points to the type of an array or struct initializer.
+ /// The Decl is determined contextually.
+ node_offset_init_ty: i32,
pub const nodeOffset = if (TracedOffset.want_tracing) nodeOffsetDebug else nodeOffsetRelease;
@@ -3124,6 +3144,7 @@ pub const LazySrcLoc = union(enum) {
.node_offset_ptr_hostsize,
.node_offset_container_tag,
.node_offset_field_default,
+ .node_offset_init_ty,
=> .{
.file_scope = decl.getFileScope(),
.parent_decl_node = decl.src_node,
@@ -4673,6 +4694,15 @@ pub fn importFile(
cur_file: *File,
import_string: []const u8,
) !ImportFileResult {
+ if (std.mem.eql(u8, import_string, "std")) {
+ return mod.importPkg(mod.main_pkg.table.get("std").?);
+ }
+ if (std.mem.eql(u8, import_string, "builtin")) {
+ return mod.importPkg(mod.main_pkg.table.get("builtin").?);
+ }
+ if (std.mem.eql(u8, import_string, "root")) {
+ return mod.importPkg(mod.root_pkg);
+ }
if (cur_file.pkg.table.get(import_string)) |pkg| {
return mod.importPkg(pkg);
}
diff --git a/src/Sema.zig b/src/Sema.zig
index ad8beff0f3..59b312e000 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -875,10 +875,6 @@ fn analyzeBodyInner(
.add => try sema.zirArithmetic(block, inst, .add),
.addwrap => try sema.zirArithmetic(block, inst, .addwrap),
.add_sat => try sema.zirArithmetic(block, inst, .add_sat),
- .div => try sema.zirArithmetic(block, inst, .div),
- .div_exact => try sema.zirArithmetic(block, inst, .div_exact),
- .div_floor => try sema.zirArithmetic(block, inst, .div_floor),
- .div_trunc => try sema.zirArithmetic(block, inst, .div_trunc),
.mod_rem => try sema.zirArithmetic(block, inst, .mod_rem),
.mod => try sema.zirArithmetic(block, inst, .mod),
.rem => try sema.zirArithmetic(block, inst, .rem),
@@ -889,6 +885,11 @@ fn analyzeBodyInner(
.subwrap => try sema.zirArithmetic(block, inst, .subwrap),
.sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
+ .div => try sema.zirDiv(block, inst),
+ .div_exact => try sema.zirDivExact(block, inst),
+ .div_floor => try sema.zirDivFloor(block, inst),
+ .div_trunc => try sema.zirDivTrunc(block, inst),
+
.maximum => try sema.zirMinMax(block, inst, .max),
.minimum => try sema.zirMinMax(block, inst, .min),
@@ -1146,6 +1147,24 @@ fn analyzeBodyInner(
i += 1;
continue;
},
+ .check_comptime_control_flow => {
+ if (!block.is_comptime) {
+ if (block.runtime_cond orelse block.runtime_loop) |runtime_src| {
+ const inst_data = sema.code.instructions.items(.data)[inst].node;
+ const src = LazySrcLoc.nodeOffset(inst_data);
+ const msg = msg: {
+ const msg = try sema.errMsg(block, src, "comptime control flow inside runtime block", .{});
+ errdefer msg.destroy(sema.gpa);
+
+ try sema.errNote(block, runtime_src, msg, "runtime control flow here", .{});
+ break :msg msg;
+ };
+ return sema.failWithOwnedErrorMsg(block, msg);
+ }
+ }
+ i += 1;
+ continue;
+ },
// Special case instructions to handle comptime control flow.
.@"break" => {
@@ -3475,19 +3494,43 @@ fn validateArrayInitTy(
block: *Block,
inst: Zir.Inst.Index,
) CompileError!void {
- const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
- const ty = try sema.resolveType(block, src, inst_data.operand);
+ const ty_src: LazySrcLoc = .{ .node_offset_init_ty = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data;
+ const ty = try sema.resolveType(block, ty_src, extra.ty);
switch (ty.zigTypeTag()) {
- .Array, .Vector => return,
+ .Array => {
+ const array_len = ty.arrayLen();
+ if (extra.init_count != array_len) {
+ return sema.fail(block, src, "expected {d} array elements; found {d}", .{
+ array_len, extra.init_count,
+ });
+ }
+ return;
+ },
+ .Vector => {
+ const array_len = ty.arrayLen();
+ if (extra.init_count != array_len) {
+ return sema.fail(block, src, "expected {d} vector elements; found {d}", .{
+ array_len, extra.init_count,
+ });
+ }
+ return;
+ },
.Struct => if (ty.isTuple()) {
- // TODO validate element count
+ const array_len = ty.arrayLen();
+ if (extra.init_count > array_len) {
+ return sema.fail(block, src, "expected at most {d} tuple fields; found {d}", .{
+ array_len, extra.init_count,
+ });
+ }
return;
},
else => {},
}
- return sema.failWithArrayInitNotSupported(block, src, ty);
+ return sema.failWithArrayInitNotSupported(block, ty_src, ty);
}
fn validateStructInitTy(
@@ -3723,6 +3766,15 @@ fn validateStructInit(
const default_val = struct_ty.structFieldDefaultValue(i);
if (default_val.tag() == .unreachable_value) {
+ if (struct_ty.isTuple()) {
+ const template = "missing tuple field with index {d}";
+ if (root_msg) |msg| {
+ try sema.errNote(block, init_src, msg, template, .{i});
+ } else {
+ root_msg = try sema.errMsg(block, init_src, template, .{i});
+ }
+ continue;
+ }
const field_name = struct_ty.structFieldName(i);
const template = "missing struct field: {s}";
const args = .{field_name};
@@ -3735,7 +3787,10 @@ fn validateStructInit(
}
const field_src = init_src; // TODO better source location
- const default_field_ptr = try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true);
+ const default_field_ptr = if (struct_ty.isTuple())
+ try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(u32, i), true)
+ else
+ try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true);
const field_ty = sema.typeOf(default_field_ptr).childType();
const init = try sema.addConstant(field_ty, default_val);
try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);
@@ -3850,6 +3905,15 @@ fn validateStructInit(
const default_val = struct_ty.structFieldDefaultValue(i);
if (default_val.tag() == .unreachable_value) {
+ if (struct_ty.isTuple()) {
+ const template = "missing tuple field with index {d}";
+ if (root_msg) |msg| {
+ try sema.errNote(block, init_src, msg, template, .{i});
+ } else {
+ root_msg = try sema.errMsg(block, init_src, template, .{i});
+ }
+ continue;
+ }
const field_name = struct_ty.structFieldName(i);
const template = "missing struct field: {s}";
const args = .{field_name};
@@ -3893,7 +3957,10 @@ fn validateStructInit(
if (field_ptr != 0) continue;
const field_src = init_src; // TODO better source location
- const default_field_ptr = try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true);
+ const default_field_ptr = if (struct_ty.isTuple())
+ try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(u32, i), true)
+ else
+ try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(u32, i), field_src, struct_ty, true);
const field_ty = sema.typeOf(default_field_ptr).childType();
const init = try sema.addConstant(field_ty, field_values[i]);
try sema.storePtr2(block, init_src, default_field_ptr, init_src, init, field_src, .store);
@@ -3916,15 +3983,24 @@ fn zirValidateArrayInit(
const array_ty = sema.typeOf(array_ptr).childType();
const array_len = array_ty.arrayLen();
- if (instrs.len != array_len) {
- if (array_ty.zigTypeTag() == .Array) {
- return sema.fail(block, init_src, "expected {d} array elements; found {d}", .{
- array_len, instrs.len,
- });
- } else {
- return sema.fail(block, init_src, "expected {d} vector elements; found {d}", .{
- array_len, instrs.len,
- });
+ if (instrs.len != array_len and array_ty.isTuple()) {
+ const struct_obj = array_ty.castTag(.tuple).?.data;
+ var root_msg: ?*Module.ErrorMsg = null;
+ for (struct_obj.values) |default_val, i| {
+ if (i < instrs.len) continue;
+
+ if (default_val.tag() == .unreachable_value) {
+ const template = "missing tuple field with index {d}";
+ if (root_msg) |msg| {
+ try sema.errNote(block, init_src, msg, template, .{i});
+ } else {
+ root_msg = try sema.errMsg(block, init_src, template, .{i});
+ }
+ }
+ }
+
+ if (root_msg) |msg| {
+ return sema.failWithOwnedErrorMsg(block, msg);
}
}
@@ -3977,10 +4053,17 @@ fn zirValidateArrayInit(
}
first_block_index = @minimum(first_block_index, block_index);
- // Array has one possible value, so value is always comptime-known
- if (opt_opv) |opv| {
- element_vals[i] = opv;
- continue;
+ if (array_ty.isTuple()) {
+ if (array_ty.structFieldValueComptime(i)) |opv| {
+ element_vals[i] = opv;
+ continue;
+ }
+ } else {
+ // Array has one possible value, so value is always comptime-known
+ if (opt_opv) |opv| {
+ element_vals[i] = opv;
+ continue;
+ }
}
// If the next instructon is a store with a comptime operand, this element
@@ -4674,10 +4757,6 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
error.OutOfMemory => return error.OutOfMemory,
else => unreachable, // we pass null for root_src_dir_path
};
- const std_pkg = mod.main_pkg.table.get("std").?;
- const builtin_pkg = mod.main_pkg.table.get("builtin").?;
- try c_import_pkg.add(sema.gpa, "builtin", builtin_pkg);
- try c_import_pkg.add(sema.gpa, "std", std_pkg);
const result = mod.importPkg(c_import_pkg) catch |err|
return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
@@ -10842,6 +10921,636 @@ fn zirArithmetic(
return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src);
}
+fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+ try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
+ try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div);
+
+ const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
+ .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
+ });
+
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const lhs_scalar_ty = lhs_ty.scalarType();
+ const rhs_scalar_ty = rhs_ty.scalarType();
+ const scalar_tag = resolved_type.scalarType().zigTypeTag();
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div);
+
+ const mod = sema.mod;
+ const target = mod.getTarget();
+ const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs);
+ const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs);
+
+ // TODO: emit compile error when .div is used on integers and there would be an
+ // ambiguous result between div_floor and div_trunc.
+
+ // For integers:
+ // If the lhs is zero, then zero is returned regardless of rhs.
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined:
+ // * if lhs type is signed:
+ // * if rhs is comptime-known and not -1, result is undefined
+ // * if rhs is -1 or runtime-known, compile error because there is a
+ // possible value (-min_int / -1) for which division would be
+ // illegal behavior.
+ // * if lhs type is unsigned, undef is returned regardless of rhs.
+ //
+ // For floats:
+ // If the rhs is zero:
+ // * comptime_float: compile error for division by zero.
+ // * other float type:
+ // * if the lhs is zero: QNaN
+ // * otherwise: +Inf or -Inf depending on lhs sign
+ // If the rhs is undefined:
+ // * comptime_float: compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // * other float type: result is undefined
+ // If the lhs is undefined, result is undefined.
+ switch (scalar_tag) {
+ .Int, .ComptimeInt, .ComptimeFloat => {
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.addConstant(resolved_type, Value.zero);
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ // TODO: if the RHS is one, return the LHS directly
+ }
+ },
+ else => {},
+ }
+
+ const runtime_src = rs: {
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
+ if (maybe_rhs_val) |rhs_val| {
+ if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
+ return sema.addConstUndef(resolved_type);
+ }
+ }
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ return sema.addConstUndef(resolved_type);
+ }
+
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
+ );
+ } else {
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target),
+ );
+ }
+ } else {
+ break :rs rhs_src;
+ }
+ } else {
+ break :rs lhs_src;
+ }
+ };
+
+ try sema.requireRuntimeBlock(block, src, runtime_src);
+
+ if (block.wantSafety()) {
+ try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
+ try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
+ }
+
+ const air_tag = if (is_int) Air.Inst.Tag.div_trunc else switch (block.float_mode) {
+ .Optimized => Air.Inst.Tag.div_float_optimized,
+ .Strict => Air.Inst.Tag.div_float,
+ };
+ return block.addBinOp(air_tag, casted_lhs, casted_rhs);
+}
+
+fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+ try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
+ try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_exact);
+
+ const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
+ .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
+ });
+
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const lhs_scalar_ty = lhs_ty.scalarType();
+ const scalar_tag = resolved_type.scalarType().zigTypeTag();
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_exact);
+
+ const mod = sema.mod;
+ const target = mod.getTarget();
+ const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs);
+ const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs);
+
+ const runtime_src = rs: {
+ // For integers:
+ // If the lhs is zero, then zero is returned regardless of rhs.
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, compile error because there is a possible
+ // value for which the division would result in a remainder.
+ // TODO: emit runtime safety for if there is a remainder
+ // TODO: emit runtime safety for division by zero
+ //
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, compile error because there is a possible
+ // value for which the division would result in a remainder.
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ } else {
+ if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.addConstant(resolved_type, Value.zero);
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ // TODO: if the RHS is one, return the LHS directly
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ // TODO: emit compile error if there is a remainder
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
+ );
+ } else {
+ // TODO: emit compile error if there is a remainder
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target),
+ );
+ }
+ } else break :rs rhs_src;
+ } else break :rs lhs_src;
+ };
+
+ try sema.requireRuntimeBlock(block, src, runtime_src);
+
+ // Depending on whether safety is enabled, we will have a slightly different strategy
+ // here. The `div_exact` AIR instruction causes undefined behavior if a remainder
+ // is produced, so in the safety check case, it cannot be used. Instead we do a
+ // div_trunc and check for remainder.
+
+ if (block.wantSafety()) {
+ try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
+ try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
+
+ const result = try block.addBinOp(.div_trunc, casted_lhs, casted_rhs);
+ const ok = if (!is_int) ok: {
+ const floored = try block.addUnOp(.floor, result);
+
+ if (resolved_type.zigTypeTag() == .Vector) {
+ const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type));
+ break :ok try block.addInst(.{
+ .tag = switch (block.float_mode) {
+ .Strict => .reduce,
+ .Optimized => .reduce_optimized,
+ },
+ .data = .{ .reduce = .{
+ .operand = eql,
+ .operation = .And,
+ } },
+ });
+ } else {
+ const is_in_range = try block.addBinOp(switch (block.float_mode) {
+ .Strict => .cmp_eq,
+ .Optimized => .cmp_eq_optimized,
+ }, result, floored);
+ break :ok is_in_range;
+ }
+ } else ok: {
+ const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs);
+
+ if (resolved_type.zigTypeTag() == .Vector) {
+ const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
+ const zero = try sema.addConstant(resolved_type, zero_val);
+ const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type));
+ break :ok try block.addInst(.{
+ .tag = .reduce,
+ .data = .{ .reduce = .{
+ .operand = eql,
+ .operation = .And,
+ } },
+ });
+ } else {
+ const zero = try sema.addConstant(resolved_type, Value.zero);
+ const is_in_range = try block.addBinOp(.cmp_eq, remainder, zero);
+ break :ok is_in_range;
+ }
+ };
+ try sema.addSafetyCheck(block, ok, .exact_division_remainder);
+ return result;
+ }
+
+ return block.addBinOp(airTag(block, is_int, .div_exact, .div_exact_optimized), casted_lhs, casted_rhs);
+}
+
+fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+ try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
+ try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_floor);
+
+ const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
+ .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
+ });
+
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const lhs_scalar_ty = lhs_ty.scalarType();
+ const rhs_scalar_ty = rhs_ty.scalarType();
+ const scalar_tag = resolved_type.scalarType().zigTypeTag();
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_floor);
+
+ const mod = sema.mod;
+ const target = mod.getTarget();
+ const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs);
+ const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs);
+
+ const runtime_src = rs: {
+ // For integers:
+ // If the lhs is zero, then zero is returned regardless of rhs.
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined:
+ // * if lhs type is signed:
+ // * if rhs is comptime-known and not -1, result is undefined
+ // * if rhs is -1 or runtime-known, compile error because there is a
+ // possible value (-min_int / -1) for which division would be
+ // illegal behavior.
+ // * if lhs type is unsigned, undef is returned regardless of rhs.
+ // TODO: emit runtime safety for division by zero
+ //
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.addConstant(resolved_type, Value.zero);
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ // TODO: if the RHS is one, return the LHS directly
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
+ if (maybe_rhs_val) |rhs_val| {
+ if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
+ return sema.addConstUndef(resolved_type);
+ }
+ }
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ return sema.addConstUndef(resolved_type);
+ }
+
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target),
+ );
+ } else {
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target),
+ );
+ }
+ } else break :rs rhs_src;
+ } else break :rs lhs_src;
+ };
+
+ try sema.requireRuntimeBlock(block, src, runtime_src);
+
+ if (block.wantSafety()) {
+ try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
+ try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
+ }
+
+ return block.addBinOp(airTag(block, is_int, .div_floor, .div_floor_optimized), casted_lhs, casted_rhs);
+}
+
+fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
+ const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
+ const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
+ const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = try sema.resolveInst(extra.lhs);
+ const rhs = try sema.resolveInst(extra.rhs);
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
+ const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
+ try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
+ try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_trunc);
+
+ const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
+ const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
+ .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
+ });
+
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const lhs_scalar_ty = lhs_ty.scalarType();
+ const rhs_scalar_ty = rhs_ty.scalarType();
+ const scalar_tag = resolved_type.scalarType().zigTypeTag();
+
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+
+ try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_trunc);
+
+ const mod = sema.mod;
+ const target = mod.getTarget();
+ const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs);
+ const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs);
+
+ const runtime_src = rs: {
+ // For integers:
+ // If the lhs is zero, then zero is returned regardless of rhs.
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined:
+ // * if lhs type is signed:
+ // * if rhs is comptime-known and not -1, result is undefined
+ // * if rhs is -1 or runtime-known, compile error because there is a
+ // possible value (-min_int / -1) for which division would be
+ // illegal behavior.
+ // * if lhs type is unsigned, undef is returned regardless of rhs.
+ // TODO: emit runtime safety for division by zero
+ //
+ // For floats:
+ // If the rhs is zero, compile error for division by zero.
+ // If the rhs is undefined, compile error because there is a possible
+ // value (zero) for which the division would be illegal behavior.
+ // If the lhs is undefined, result is undefined.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.isUndef()) {
+ if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.addConstant(resolved_type, Value.zero);
+ }
+ }
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndef()) {
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
+ return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndef()) {
+ if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
+ if (maybe_rhs_val) |rhs_val| {
+ if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
+ return sema.addConstUndef(resolved_type);
+ }
+ }
+ return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ return sema.addConstUndef(resolved_type);
+ }
+
+ if (maybe_rhs_val) |rhs_val| {
+ if (is_int) {
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
+ );
+ } else {
+ return sema.addConstant(
+ resolved_type,
+ try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target),
+ );
+ }
+ } else break :rs rhs_src;
+ } else break :rs lhs_src;
+ };
+
+ try sema.requireRuntimeBlock(block, src, runtime_src);
+
+ if (block.wantSafety()) {
+ try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
+ try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
+ }
+
+ return block.addBinOp(airTag(block, is_int, .div_trunc, .div_trunc_optimized), casted_lhs, casted_rhs);
+}
+
+fn addDivIntOverflowSafety(
+ sema: *Sema,
+ block: *Block,
+ resolved_type: Type,
+ lhs_scalar_ty: Type,
+ maybe_lhs_val: ?Value,
+ maybe_rhs_val: ?Value,
+ casted_lhs: Air.Inst.Ref,
+ casted_rhs: Air.Inst.Ref,
+ is_int: bool,
+) CompileError!void {
+ if (!is_int) return;
+
+ // If the LHS is unsigned, it cannot cause overflow.
+ if (!lhs_scalar_ty.isSignedInt()) return;
+
+ const mod = sema.mod;
+ const target = mod.getTarget();
+
+ // If the LHS is widened to a larger integer type, no overflow is possible.
+ if (lhs_scalar_ty.intInfo(target).bits < resolved_type.intInfo(target).bits) {
+ return;
+ }
+
+ const min_int = try resolved_type.minInt(sema.arena, target);
+ const neg_one_scalar = try Value.Tag.int_i64.create(sema.arena, -1);
+ const neg_one = if (resolved_type.zigTypeTag() == .Vector)
+ try Value.Tag.repeated.create(sema.arena, neg_one_scalar)
+ else
+ neg_one_scalar;
+
+ // If the LHS is comptime-known to be not equal to the min int,
+ // no overflow is possible.
+ if (maybe_lhs_val) |lhs_val| {
+ if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) return;
+ }
+
+ // If the RHS is comptime-known to not be equal to -1, no overflow is possible.
+ if (maybe_rhs_val) |rhs_val| {
+ if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) return;
+ }
+
+ var ok: Air.Inst.Ref = .none;
+ if (resolved_type.zigTypeTag() == .Vector) {
+ const vector_ty_ref = try sema.addType(resolved_type);
+ if (maybe_lhs_val == null) {
+ const min_int_ref = try sema.addConstant(resolved_type, min_int);
+ ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref);
+ }
+ if (maybe_rhs_val == null) {
+ const neg_one_ref = try sema.addConstant(resolved_type, neg_one);
+ const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref);
+ if (ok == .none) {
+ ok = rhs_ok;
+ } else {
+ ok = try block.addBinOp(.bool_or, ok, rhs_ok);
+ }
+ }
+ assert(ok != .none);
+ ok = try block.addInst(.{
+ .tag = .reduce,
+ .data = .{ .reduce = .{
+ .operand = ok,
+ .operation = .And,
+ } },
+ });
+ } else {
+ if (maybe_lhs_val == null) {
+ const min_int_ref = try sema.addConstant(resolved_type, min_int);
+ ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref);
+ }
+ if (maybe_rhs_val == null) {
+ const neg_one_ref = try sema.addConstant(resolved_type, neg_one);
+ const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref);
+ if (ok == .none) {
+ ok = rhs_ok;
+ } else {
+ ok = try block.addBinOp(.bool_or, ok, rhs_ok);
+ }
+ }
+ assert(ok != .none);
+ }
+ try sema.addSafetyCheck(block, ok, .integer_overflow);
+}
+
+fn addDivByZeroSafety(
+ sema: *Sema,
+ block: *Block,
+ resolved_type: Type,
+ maybe_rhs_val: ?Value,
+ casted_rhs: Air.Inst.Ref,
+ is_int: bool,
+) CompileError!void {
+ // Strict IEEE floats have well-defined division by zero.
+ if (!is_int and block.float_mode == .Strict) return;
+
+ // If rhs was comptime-known to be zero a compile error would have been
+ // emitted above.
+ if (maybe_rhs_val != null) return;
+
+ const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
+ const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
+ const zero = try sema.addConstant(resolved_type, zero_val);
+ const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type));
+ break :ok try block.addInst(.{
+ .tag = if (is_int) .reduce else .reduce_optimized,
+ .data = .{ .reduce = .{
+ .operand = ok,
+ .operation = .And,
+ } },
+ });
+ } else ok: {
+ const zero = try sema.addConstant(resolved_type, Value.zero);
+ break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero);
+ };
+ try sema.addSafetyCheck(block, ok, .divide_by_zero);
+}
+
+fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst.Tag) Air.Inst.Tag {
+ if (is_int) return normal;
+ return switch (block.float_mode) {
+ .Strict => normal,
+ .Optimized => optimized,
+ };
+}
+
fn zirOverflowArithmetic(
sema: *Sema,
block: *Block,
@@ -11108,13 +11817,8 @@ fn analyzeArithmetic(
const scalar_tag = resolved_type.scalarType().zigTypeTag();
const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
- const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
- if (!is_int and !(is_float and floatOpAllowed(zir_tag))) {
- return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
- @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
- });
- }
+ try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, zir_tag);
const mod = sema.mod;
const target = mod.getTarget();
@@ -11321,277 +12025,6 @@ fn analyzeArithmetic(
} else break :rs .{ .src = rhs_src, .air_tag = .sub_sat };
} else break :rs .{ .src = lhs_src, .air_tag = .sub_sat };
},
- .div => {
- // TODO: emit compile error when .div is used on integers and there would be an
- // ambiguous result between div_floor and div_trunc.
-
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined:
- // * if lhs type is signed:
- // * if rhs is comptime-known and not -1, result is undefined
- // * if rhs is -1 or runtime-known, compile error because there is a
- // possible value (-min_int / -1) for which division would be
- // illegal behavior.
- // * if lhs type is unsigned, undef is returned regardless of rhs.
- // TODO: emit runtime safety for division by zero
- //
- // For floats:
- // If the rhs is zero:
- // * comptime_float: compile error for division by zero.
- // * other float type:
- // * if the lhs is zero: QNaN
- // * otherwise: +Inf or -Inf depending on lhs sign
- // If the rhs is undefined:
- // * comptime_float: compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // * other float type: result is undefined
- // If the lhs is undefined, result is undefined.
- switch (scalar_tag) {
- .Int, .ComptimeInt, .ComptimeFloat => {
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef()) {
- if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.addConstant(resolved_type, Value.zero);
- }
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef()) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- }
- },
- else => {},
- }
-
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef()) {
- if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
- if (maybe_rhs_val) |rhs_val| {
- if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
- return sema.addConstUndef(resolved_type);
- }
- }
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- return sema.addConstUndef(resolved_type);
- }
-
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- return sema.addConstant(
- resolved_type,
- try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
- );
- } else {
- return sema.addConstant(
- resolved_type,
- try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target),
- );
- }
- } else {
- if (is_int) {
- break :rs .{ .src = rhs_src, .air_tag = .div_trunc };
- } else {
- break :rs .{ .src = rhs_src, .air_tag = if (block.float_mode == .Optimized) .div_float_optimized else .div_float };
- }
- }
- } else {
- if (is_int) {
- break :rs .{ .src = lhs_src, .air_tag = .div_trunc };
- } else {
- break :rs .{ .src = lhs_src, .air_tag = if (block.float_mode == .Optimized) .div_float_optimized else .div_float };
- }
- }
- },
- .div_trunc => {
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined:
- // * if lhs type is signed:
- // * if rhs is comptime-known and not -1, result is undefined
- // * if rhs is -1 or runtime-known, compile error because there is a
- // possible value (-min_int / -1) for which division would be
- // illegal behavior.
- // * if lhs type is unsigned, undef is returned regardless of rhs.
- // TODO: emit runtime safety for division by zero
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, result is undefined.
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef()) {
- if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.addConstant(resolved_type, Value.zero);
- }
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef()) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- }
- const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_trunc_optimized else .div_trunc;
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef()) {
- if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
- if (maybe_rhs_val) |rhs_val| {
- if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
- return sema.addConstUndef(resolved_type);
- }
- }
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- return sema.addConstUndef(resolved_type);
- }
-
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- return sema.addConstant(
- resolved_type,
- try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
- );
- } else {
- return sema.addConstant(
- resolved_type,
- try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target),
- );
- }
- } else break :rs .{ .src = rhs_src, .air_tag = air_tag };
- } else break :rs .{ .src = lhs_src, .air_tag = air_tag };
- },
- .div_floor => {
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined:
- // * if lhs type is signed:
- // * if rhs is comptime-known and not -1, result is undefined
- // * if rhs is -1 or runtime-known, compile error because there is a
- // possible value (-min_int / -1) for which division would be
- // illegal behavior.
- // * if lhs type is unsigned, undef is returned regardless of rhs.
- // TODO: emit runtime safety for division by zero
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, result is undefined.
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef()) {
- if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.addConstant(resolved_type, Value.zero);
- }
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef()) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- }
- const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_floor_optimized else .div_floor;
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef()) {
- if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
- if (maybe_rhs_val) |rhs_val| {
- if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
- return sema.addConstUndef(resolved_type);
- }
- }
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- return sema.addConstUndef(resolved_type);
- }
-
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- return sema.addConstant(
- resolved_type,
- try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target),
- );
- } else {
- return sema.addConstant(
- resolved_type,
- try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target),
- );
- }
- } else break :rs .{ .src = rhs_src, .air_tag = air_tag };
- } else break :rs .{ .src = lhs_src, .air_tag = air_tag };
- },
- .div_exact => {
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, compile error because there is a possible
- // value for which the division would result in a remainder.
- // TODO: emit runtime safety for if there is a remainder
- // TODO: emit runtime safety for division by zero
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, compile error because there is a possible
- // value for which the division would result in a remainder.
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef()) {
- return sema.failWithUseOfUndef(block, rhs_src);
- } else {
- if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.addConstant(resolved_type, Value.zero);
- }
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef()) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- }
- const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_exact_optimized else .div_exact;
- if (maybe_lhs_val) |lhs_val| {
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- // TODO: emit compile error if there is a remainder
- return sema.addConstant(
- resolved_type,
- try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
- );
- } else {
- // TODO: emit compile error if there is a remainder
- return sema.addConstant(
- resolved_type,
- try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target),
- );
- }
- } else break :rs .{ .src = rhs_src, .air_tag = air_tag };
- } else break :rs .{ .src = lhs_src, .air_tag = air_tag };
- },
.mul => {
// For integers:
// If either of the operands are zero, the result is zero.
@@ -11970,28 +12403,6 @@ fn analyzeArithmetic(
}
}
switch (rs.air_tag) {
- // zig fmt: off
- .div_float, .div_exact, .div_trunc, .div_floor, .div_float_optimized,
- .div_exact_optimized, .div_trunc_optimized, .div_floor_optimized
- // zig fmt: on
- => if (scalar_tag == .Int or block.float_mode == .Optimized) {
- const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
- const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
- const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val);
- const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type));
- break :ok try block.addInst(.{
- .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
- .data = .{ .reduce = .{
- .operand = ok,
- .operation = .And,
- } },
- });
- } else ok: {
- const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero);
- break :ok try block.addBinOp(if (block.float_mode == .Optimized) .cmp_neq_optimized else .cmp_neq, casted_rhs, zero);
- };
- try sema.addSafetyCheck(block, ok, .divide_by_zero);
- },
.rem, .mod, .rem_optimized, .mod_optimized => {
const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
@@ -12018,47 +12429,6 @@ fn analyzeArithmetic(
},
else => {},
}
- if (rs.air_tag == .div_exact or rs.air_tag == .div_exact_optimized) {
- const result = try block.addBinOp(.div_exact, casted_lhs, casted_rhs);
- const ok = if (scalar_tag == .Float) ok: {
- const floored = try block.addUnOp(.floor, result);
-
- if (resolved_type.zigTypeTag() == .Vector) {
- const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type));
- break :ok try block.addInst(.{
- .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
- .data = .{ .reduce = .{
- .operand = eql,
- .operation = .And,
- } },
- });
- } else {
- const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, result, floored);
- break :ok is_in_range;
- }
- } else ok: {
- const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs);
-
- if (resolved_type.zigTypeTag() == .Vector) {
- const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
- const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val);
- const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type));
- break :ok try block.addInst(.{
- .tag = .reduce,
- .data = .{ .reduce = .{
- .operand = eql,
- .operation = .And,
- } },
- });
- } else {
- const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero);
- const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, remainder, zero);
- break :ok is_in_range;
- }
- };
- try sema.addSafetyCheck(block, ok, .exact_division_remainder);
- return result;
- }
}
return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs);
}
@@ -14696,6 +15066,22 @@ fn finishStructInit(
field_inits[i] = try sema.addConstant(struct_obj.types[i], default_val);
}
}
+ } else if (struct_ty.isTuple()) {
+ const struct_obj = struct_ty.castTag(.tuple).?.data;
+ for (struct_obj.values) |default_val, i| {
+ if (field_inits[i] != .none) continue;
+
+ if (default_val.tag() == .unreachable_value) {
+ const template = "missing tuple field with index {d}";
+ if (root_msg) |msg| {
+ try sema.errNote(block, init_src, msg, template, .{i});
+ } else {
+ root_msg = try sema.errMsg(block, init_src, template, .{i});
+ }
+ } else {
+ field_inits[i] = try sema.addConstant(struct_obj.types[i], default_val);
+ }
+ }
} else {
const struct_obj = struct_ty.castTag(.@"struct").?.data;
for (struct_obj.fields.values()) |field, i| {
@@ -16710,6 +17096,46 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr
}
}
+fn checkInvalidPtrArithmetic(
+ sema: *Sema,
+ block: *Block,
+ src: LazySrcLoc,
+ ty: Type,
+ zir_tag: Zir.Inst.Tag,
+) CompileError!void {
+ switch (try ty.zigTypeTagOrPoison()) {
+ .Pointer => switch (ty.ptrSize()) {
+ .One, .Slice => return,
+ .Many, .C => return sema.fail(
+ block,
+ src,
+ "invalid pointer arithmetic operand: '{s}''",
+ .{@tagName(zir_tag)},
+ ),
+ },
+ else => return,
+ }
+}
+
+fn checkArithmeticOp(
+ sema: *Sema,
+ block: *Block,
+ src: LazySrcLoc,
+ scalar_tag: std.builtin.TypeId,
+ lhs_zig_ty_tag: std.builtin.TypeId,
+ rhs_zig_ty_tag: std.builtin.TypeId,
+ zir_tag: Zir.Inst.Tag,
+) CompileError!void {
+ const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
+ const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
+
+ if (!is_int and !(is_float and floatOpAllowed(zir_tag))) {
+ return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
+ @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
+ });
+ }
+}
+
fn checkPtrOperand(
sema: *Sema,
block: *Block,
@@ -20241,7 +20667,7 @@ fn tupleFieldVal(
return tupleFieldValByIndex(sema, block, src, tuple_byval, field_index, tuple_ty);
}
-/// Don't forget to check for "len" before calling this.
+/// Asserts that `field_name` is not "len".
fn tupleFieldIndex(
sema: *Sema,
block: *Block,
@@ -20249,8 +20675,12 @@ fn tupleFieldIndex(
field_name: []const u8,
field_name_src: LazySrcLoc,
) CompileError!u32 {
+ assert(!std.mem.eql(u8, field_name, "len"));
if (std.fmt.parseUnsigned(u32, field_name, 10)) |field_index| {
if (field_index < tuple_ty.structFieldCount()) return field_index;
+ return sema.fail(block, field_name_src, "index '{s}' out of bounds of tuple '{}'", .{
+ field_name, tuple_ty.fmt(sema.mod),
+ });
} else |_| {}
return sema.fail(block, field_name_src, "no field named '{s}' in tuple '{}'", .{
diff --git a/src/Zir.zig b/src/Zir.zig
index 6e9b133310..ccd677df0b 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -280,6 +280,9 @@ pub const Inst = struct {
/// break instruction in a block, and the target block is the parent.
/// Uses the `break` union field.
break_inline,
+ /// Checks that comptime control flow does not happen inside a runtime block.
+ /// Uses the `node` union field.
+ check_comptime_control_flow,
/// Function call.
/// Uses the `pl_node` union field with payload `Call`.
/// AST node is the function call.
@@ -1266,6 +1269,7 @@ pub const Inst = struct {
.repeat_inline,
.panic,
.panic_comptime,
+ .check_comptime_control_flow,
=> true,
};
}
@@ -1315,6 +1319,7 @@ pub const Inst = struct {
.set_runtime_safety,
.memcpy,
.memset,
+ .check_comptime_control_flow,
=> true,
.param,
@@ -1595,6 +1600,7 @@ pub const Inst = struct {
.bool_br_or = .bool_br,
.@"break" = .@"break",
.break_inline = .@"break",
+ .check_comptime_control_flow = .node,
.call = .pl_node,
.cmp_lt = .pl_node,
.cmp_lte = .pl_node,
@@ -1703,7 +1709,7 @@ pub const Inst = struct {
.switch_capture_multi_ref = .switch_capture,
.array_base_ptr = .un_node,
.field_base_ptr = .un_node,
- .validate_array_init_ty = .un_node,
+ .validate_array_init_ty = .pl_node,
.validate_struct_init_ty = .un_node,
.validate_struct_init = .pl_node,
.validate_struct_init_comptime = .pl_node,
@@ -3537,6 +3543,11 @@ pub const Inst = struct {
line: u32,
column: u32,
};
+
+ pub const ArrayInit = struct {
+ ty: Ref,
+ init_count: u32,
+ };
};
pub const SpecialProng = enum { none, @"else", under };
diff --git a/src/main.zig b/src/main.zig
index 87349df38a..f192137b3c 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -873,6 +873,12 @@ fn buildOutputType(
) catch |err| {
fatal("Failed to add package at path {s}: {s}", .{ pkg_path.?, @errorName(err) });
};
+
+ if (mem.eql(u8, pkg_name.?, "std") or mem.eql(u8, pkg_name.?, "root") or mem.eql(u8, pkg_name.?, "builtin")) {
+ fatal("unable to add package '{s}' -> '{s}': conflicts with builtin package", .{ pkg_name.?, pkg_path.? });
+ } else if (cur_pkg.table.get(pkg_name.?)) |prev| {
+ fatal("unable to add package '{s}' -> '{s}': already exists as '{s}", .{ pkg_name.?, pkg_path.?, prev.root_src_path });
+ }
try cur_pkg.addAndAdopt(gpa, pkg_name.?, new_cur_pkg);
cur_pkg = new_cur_pkg;
} else if (mem.eql(u8, arg, "--pkg-end")) {
diff --git a/src/print_zir.zig b/src/print_zir.zig
index de51c271c4..6e33154bbd 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -229,7 +229,6 @@ const Writer = struct {
.switch_cond_ref,
.array_base_ptr,
.field_base_ptr,
- .validate_array_init_ty,
.validate_struct_init_ty,
.make_ptr_const,
.validate_deref,
@@ -246,6 +245,7 @@ const Writer = struct {
.bool_br_or,
=> try self.writeBoolBr(stream, inst),
+ .validate_array_init_ty => try self.writeValidateArrayInitTy(stream, inst),
.array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst),
.param_type => try self.writeParamType(stream, inst),
.ptr_type => try self.writePtrType(stream, inst),
@@ -409,6 +409,7 @@ const Writer = struct {
.alloc_inferred_comptime_mut,
.ret_ptr,
.ret_type,
+ .check_comptime_control_flow,
=> try self.writeNode(stream, inst),
.error_value,
@@ -576,6 +577,18 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
+ fn writeValidateArrayInitTy(
+ self: *Writer,
+ stream: anytype,
+ inst: Zir.Inst.Index,
+ ) (@TypeOf(stream).Error || error{OutOfMemory})!void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data;
+ try self.writeInstRef(stream, extra.ty);
+ try stream.print(", {d}) ", .{extra.init_count});
+ try self.writeSrc(stream, inst_data.src());
+ }
+
fn writeArrayTypeSentinel(
self: *Writer,
stream: anytype,
diff --git a/src/stage1/parser.hpp b/src/stage1/parser.hpp
index 8ac8ce6de1..065f951e91 100644
--- a/src/stage1/parser.hpp
+++ b/src/stage1/parser.hpp
@@ -14,8 +14,6 @@
AstNode * ast_parse(Buf *buf, ZigType *owner, ErrColor err_color);
-void ast_print(AstNode *node, int indent);
-
void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *context), void *context);
Buf *node_identifier_buf(AstNode *node);
diff --git a/src/type.zig b/src/type.zig
index 6750ec724b..3b1b0dd59d 100644
--- a/src/type.zig
+++ b/src/type.zig
@@ -5201,10 +5201,20 @@ pub const Type = extern union {
};
}
+ // Works for vectors and vectors of integers.
+ pub fn minInt(ty: Type, arena: Allocator, target: Target) !Value {
+ const scalar = try minIntScalar(ty.scalarType(), arena, target);
+ if (ty.zigTypeTag() == .Vector) {
+ return Value.Tag.repeated.create(arena, scalar);
+ } else {
+ return scalar;
+ }
+ }
+
/// Asserts that self.zigTypeTag() == .Int.
- pub fn minInt(self: Type, arena: Allocator, target: Target) !Value {
- assert(self.zigTypeTag() == .Int);
- const info = self.intInfo(target);
+ pub fn minIntScalar(ty: Type, arena: Allocator, target: Target) !Value {
+ assert(ty.zigTypeTag() == .Int);
+ const info = ty.intInfo(target);
if (info.signedness == .unsigned) {
return Value.zero;