diff --git a/src/Air.zig b/src/Air.zig index a0fb512934..39215495c3 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -637,6 +637,15 @@ pub const Inst = struct { /// Uses the `pl_op` field, payload represents the index of the target memory. wasm_memory_grow, + /// Returns `true` if and only if the operand, an integer with + /// the same size as the error integer type, is less than the + /// total number of errors in the Module. + /// Result type is always `bool`. + /// Uses the `un_op` field. + /// Note that the number of errors in the Module cannot be considered stable until + /// flush(). + cmp_lt_errors_len, + pub fn fromCmpOp(op: std.math.CompareOperator) Tag { return switch (op) { .lt => .cmp_lt, @@ -928,6 +937,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .cmp_gte, .cmp_gt, .cmp_neq, + .cmp_lt_errors_len, .is_null, .is_non_null, .is_null_ptr, @@ -936,9 +946,9 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .is_non_err, .is_err_ptr, .is_non_err_ptr, - => return Type.initTag(.bool), + => return Type.bool, - .const_ty => return Type.initTag(.type), + .const_ty => return Type.type, .alloc, .ret_ptr, diff --git a/src/AstGen.zig b/src/AstGen.zig index 6c75cc233c..ed6c9f86ce 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7250,7 +7250,7 @@ fn builtinCall( .ptr_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ptr_to_int), .error_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .error_to_int), - .int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type }, params[0], .int_to_error), + .int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .u16_type }, params[0], .int_to_error), .compile_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .compile_error), .set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .u32_type }, params[0], .set_eval_branch_quota), .enum_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .enum_to_int), diff --git a/src/Liveness.zig b/src/Liveness.zig index 4b099baf6d..59f7f5be91 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -393,6 +393,7 @@ fn analyzeInst( .ceil, .round, .trunc_float, + .cmp_lt_errors_len, => { const operand = inst_datas[inst].un_op; return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index e1e77f4c11..7ad90cdf09 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5640,7 +5640,7 @@ fn zirErrorToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const op = sema.resolveInst(inst_data.operand); const op_coerced = try sema.coerce(block, Type.anyerror, op, operand_src); - const result_ty = Type.initTag(.u16); + const result_ty = Type.u16; if (try sema.resolveMaybeUndefVal(block, src, op_coerced)) |val| { if (val.isUndef()) { @@ -5665,32 +5665,31 @@ fn zirIntToError(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - - const op = sema.resolveInst(inst_data.operand); + const uncasted_operand = sema.resolveInst(inst_data.operand); + const operand = try sema.coerce(block, Type.u16, uncasted_operand, operand_src); const target = sema.mod.getTarget(); - if (try sema.resolveDefinedValue(block, operand_src, op)) |value| { - const int = value.toUnsignedInt(target); + if (try sema.resolveDefinedValue(block, operand_src, operand)) |value| { + const int = try sema.usizeCast(block, operand_src, value.toUnsignedInt(target)); if (int > sema.mod.global_error_set.count() or int == 0) return sema.fail(block, operand_src, "integer value {d} represents no error", .{int}); const payload = try sema.arena.create(Value.Payload.Error); payload.* = .{ .base = .{ .tag = .@"error" }, - .data = .{ .name = sema.mod.error_name_list.items[@intCast(usize, int)] }, + .data = .{ .name = sema.mod.error_name_list.items[int] }, }; return sema.addConstant(Type.anyerror, Value.initPayload(&payload.base)); } try sema.requireRuntimeBlock(block, src); if (block.wantSafety()) { - return sema.fail(block, src, "TODO: get max errors in compilation", .{}); - // const is_gt_max = @panic("TODO get max errors in compilation"); - // try sema.addSafetyCheck(block, is_gt_max, .invalid_error_code); + const is_lt_len = try block.addUnOp(.cmp_lt_errors_len, operand); + try sema.addSafetyCheck(block, is_lt_len, .invalid_error_code); } return block.addInst(.{ .tag = .bitcast, .data = .{ .ty_op = .{ .ty = Air.Inst.Ref.anyerror_type, - .operand = op, + .operand = operand, } }, }); } diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index da6da9c877..142611d083 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -570,7 +570,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .cmp_gte => try self.airCmp(inst, .gte), .cmp_gt => try self.airCmp(inst, .gt), .cmp_neq => try self.airCmp(inst, .neq), + .cmp_vector => try self.airCmpVector(inst), + .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), .bool_and => try self.airBinOp(inst), .bool_or => try self.airBinOp(inst), @@ -2660,6 +2662,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void { return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch}); } +fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + _ = operand; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch}); + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 15d0e47d4a..6e03dbf4fa 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -567,7 +567,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .cmp_gte => try self.airCmp(inst, .gte), .cmp_gt => try self.airCmp(inst, .gt), .cmp_neq => try self.airCmp(inst, .neq), + .cmp_vector => try self.airCmpVector(inst), + .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), .bool_and => try self.airBinOp(inst), .bool_or => try self.airBinOp(inst), @@ -3063,6 +3065,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void { return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch}); } +fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + _ = operand; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch}); + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index fa243819cf..2c8374fca1 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -537,7 +537,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .cmp_gte => try self.airCmp(inst, .gte), .cmp_gt => try self.airCmp(inst, .gt), .cmp_neq => try self.airCmp(inst, .neq), + .cmp_vector => try self.airCmpVector(inst), + .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), .bool_and => try self.airBoolOp(inst), .bool_or => try self.airBoolOp(inst), @@ -1799,6 +1801,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void { return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch}); } +fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + _ = operand; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch}); + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index d43e8758a1..024a94aaf4 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1319,7 +1319,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .cmp_lte => self.airCmp(inst, .lte), .cmp_lt => self.airCmp(inst, .lt), .cmp_neq => self.airCmp(inst, .neq), + .cmp_vector => self.airCmpVector(inst), + .cmp_lt_errors_len => self.airCmpLtErrorsLen(inst), .array_elem_val => self.airArrayElemVal(inst), .array_to_slice => self.airArrayToSlice(inst), @@ -2267,6 +2269,16 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) InnerError!WValue { return self.fail("TODO implement airCmpVector for wasm", .{}); } +fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue{ .none = {} }; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + + _ = operand; + return self.fail("TODO implement airCmpLtErrorsLen for wasm", .{}); +} + fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const br = self.air.instructions.items(.data)[inst].br; const block = self.blocks.get(br.block_inst).?; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index bc0171899a..5d8864f1a2 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -693,7 +693,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .cmp_gte => try self.airCmp(inst, .gte), .cmp_gt => try self.airCmp(inst, .gt), .cmp_neq => try self.airCmp(inst, .neq), + .cmp_vector => try self.airCmpVector(inst), + .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), .bool_and => try self.airBoolOp(inst), .bool_or => try self.airBoolOp(inst), @@ -3818,6 +3820,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void { return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch}); } +fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + _ = operand; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch}); + return self.finishAir(inst, result, .{ un_op, .none, .none }); +} + fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt; const payload = try self.addExtra(Mir.DbgLineColumn{ diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 38a105c172..19556fe8c4 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1767,7 +1767,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .cmp_eq => try airEquality(f, inst, "((", "=="), .cmp_neq => try airEquality(f, inst, "!((", "!="), - .cmp_vector => return f.fail("TODO: C backend: implement binary op for tag '{s}'", .{@tagName(Air.Inst.Tag.cmp_vector)}), + .cmp_vector => return f.fail("TODO: C backend: implement cmp_vector", .{}), + .cmp_lt_errors_len => return f.fail("TODO: C backend: implement cmp_lt_errors_len", .{}), // bool_and and bool_or are non-short-circuit operations .bool_and => try airBinOp(f, inst, " & "), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 74de485721..af68255fe8 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -440,8 +440,38 @@ pub const Object = struct { error_name_table_ptr_global.setInitializer(error_name_table_ptr); } + fn genCmpLtErrorsLenFunction(object: *Object, comp: *Compilation) !void { + // If there is no such function in the module, it means the source code does not need it. + const llvm_fn = object.llvm_module.getNamedFunction(lt_errors_fn_name) orelse return; + const mod = comp.bin_file.options.module.?; + const errors_len = mod.global_error_set.count(); + + // Delete previous implementation. We replace it with every flush() because the + // total number of errors may have changed. + while (llvm_fn.getFirstBasicBlock()) |bb| { + bb.deleteBasicBlock(); + } + + const builder = object.context.createBuilder(); + + const entry_block = object.context.appendBasicBlock(llvm_fn, "Entry"); + builder.positionBuilderAtEnd(entry_block); + builder.clearCurrentDebugLocation(); + + // Example source of the following LLVM IR: + // fn __zig_lt_errors_len(index: u16) bool { + // return index < total_errors_len; + // } + + const lhs = llvm_fn.getParam(0); + const rhs = lhs.typeOf().constInt(errors_len, .False); + const is_lt = builder.buildICmp(.ULT, lhs, rhs, ""); + _ = builder.buildRet(is_lt); + } + pub fn flushModule(self: *Object, comp: *Compilation) !void { try self.genErrorNameTable(comp); + try self.genCmpLtErrorsLenFunction(comp); if (self.di_builder) |dib| { // When lowering debug info for pointers, we emitted the element types as @@ -3457,7 +3487,9 @@ pub const FuncGen = struct { .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), .cmp_neq => try self.airCmp(inst, .neq), + .cmp_vector => try self.airCmpVector(inst), + .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst), .is_non_null => try self.airIsNonNull(inst, false, false, .NE), .is_non_null_ptr => try self.airIsNonNull(inst, true , false, .NE), @@ -3738,6 +3770,16 @@ pub const FuncGen = struct { return self.cmp(lhs, rhs, vec_ty, cmp_op); } + fn airCmpLtErrorsLen(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const llvm_fn = try self.getCmpLtErrorsLenFunction(); + const args: [1]*const llvm.Value = .{operand}; + return self.builder.buildCall(llvm_fn, &args, args.len, .Fast, .Auto, ""); + } + fn cmp( self: *FuncGen, lhs: *const llvm.Value, @@ -6392,6 +6434,25 @@ pub const FuncGen = struct { return fn_val; } + fn getCmpLtErrorsLenFunction(self: *FuncGen) !*const llvm.Value { + if (self.dg.object.llvm_module.getNamedFunction(lt_errors_fn_name)) |llvm_fn| { + return llvm_fn; + } + + // Function signature: fn (anyerror) bool + + const ret_llvm_ty = try self.dg.llvmType(Type.bool); + const anyerror_llvm_ty = try self.dg.llvmType(Type.anyerror); + const param_types = [_]*const llvm.Type{anyerror_llvm_ty}; + + const fn_type = llvm.functionType(ret_llvm_ty, ¶m_types, param_types.len, .False); + const llvm_fn = self.dg.object.llvm_module.addFunction(lt_errors_fn_name, fn_type); + llvm_fn.setLinkage(.Internal); + llvm_fn.setFunctionCallConv(.Fast); + self.dg.addCommonFnAttributes(llvm_fn); + return llvm_fn; + } + fn airErrorName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -7663,3 +7724,5 @@ const AnnotatedDITypePtr = enum(usize) { return @truncate(u1, @enumToInt(self)) != 0; } }; + +const lt_errors_fn_name = "__zig_lt_errors_len"; diff --git a/src/print_air.zig b/src/print_air.zig index 6552b54faf..69d7b63b2a 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -166,6 +166,7 @@ const Writer = struct { .ceil, .round, .trunc_float, + .cmp_lt_errors_len, => try w.writeUnOp(s, inst), .breakpoint, diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 8a4bec935e..4e952828c5 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -360,7 +360,11 @@ test "expected [*c]const u8, found [*:0]const u8" { } test "explicit cast from integer to error type" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO try testCastIntToErr(error.ItBroke); comptime try testCastIntToErr(error.ItBroke);