From 05947ea870f95ab90a75e174079492f546baaf72 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Mar 2022 22:19:06 -0700 Subject: [PATCH] stage2: implement `@intToError` with safety This commit introduces a new AIR instruction `cmp_lt_errors_len`. It's specific to this use case for two reasons: * The total number of errors is not stable during semantic analysis; it can only be reliably checked when flush() is called. So the backend that is lowering the instruction must emit a relocation of some kind and then populate it during flush(). * The fewer AIR instructions in memory, the better for compiler performance, so we squish complex meanings into AIR tags without hesitation. The instruction is implemented only in the LLVM backend so far. It does this by creating a simple function which is gutted and re-populated with each flush(). AstGen now uses ResultLoc.coerced_ty for `@intToError` and Sema does the coercion. --- src/Air.zig | 14 ++++++-- src/AstGen.zig | 2 +- src/Liveness.zig | 1 + src/Sema.zig | 19 ++++++----- src/arch/aarch64/CodeGen.zig | 10 ++++++ src/arch/arm/CodeGen.zig | 10 ++++++ src/arch/riscv64/CodeGen.zig | 10 ++++++ src/arch/wasm/CodeGen.zig | 12 +++++++ src/arch/x86_64/CodeGen.zig | 10 ++++++ src/codegen/c.zig | 3 +- src/codegen/llvm.zig | 63 ++++++++++++++++++++++++++++++++++++ src/print_air.zig | 1 + test/behavior/cast.zig | 6 +++- 13 files changed, 146 insertions(+), 15 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index a0fb512934662ef6099d8e07b49534e52933d73b..39215495c341a586627b0f02bcc2df504696eb2a 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 6c75cc233c74e2868e2bf068cb84092d834068f7..ed6c9f86cebe2304ff4cc69b9673991befe17c30 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 4b099baf6dad2ed55fca29c99090d0aa9714c643..59f7f5be91b22291950d62c27a9f9c501c6efe35 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 e1e77f4c112e005e70f4e30476b21aee354d8775..7ad90cdf09900c37ea941cfd3ab1fba53b394888 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 da6da9c877a6d8e04a0c0c3afbcd6f566ca33d22..142611d0839b91c9fef68b8001b566a0acf7d146 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 15d0e47d4a8508662cbe99ea6df532d30e55cc17..6e03dbf4fa4e8205f3486ddc7ebb21f94f877152 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 fa243819cf633aacba3a9dfaf3b4c1b5c3111087..2c8374fca1c2cd7e829238b0b67af11966caf049 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 d43e8758a1a7a9d4982aa37908339d874f4b7cd2..024a94aaf47eb3402db46a8be8cf0a52d4f41258 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 bc0171899add5b87bfe86cdfc5ed84c2d906131d..5d8864f1a239b30be9408f7b1589642b847bf178 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 38a105c1726f249af5623c91b52df2bafe2c7174..19556fe8c480c7411743aa2b7024c9bb45d9b5f1 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 74de485721e128168a3640e2653ab294424d5879..af68255fe8f5b9ae536ba1174a7be899a1d1056f 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 6552b54faf47bcc5f60719458583585d583dc852..69d7b63b2a5b95b94c307d9236e0fee6b87abf2a 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 8a4bec935edba3af8a05ec1affbb576ee54ef59f..4e952828c58d9fad598de500210fdd32a2d05318 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); -- 2.54.0