authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-03-29 22:19:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-03-29 22:19:06-07:00
log05947ea870f95ab90a75e174079492f546baaf72
tree10c9a048bb1d55ce553c42c8f8e4f969241925cc
parent83617eac5902a9e66449e8c409dfa9e560bf9f12

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.

13 files changed, 146 insertions(+), 15 deletions(-)

src/Air.zig+12-2
......@@ -637,6 +637,15 @@ pub const Inst = struct {
637637 /// Uses the `pl_op` field, payload represents the index of the target memory.
638638 wasm_memory_grow,
639639
640 /// Returns `true` if and only if the operand, an integer with
641 /// the same size as the error integer type, is less than the
642 /// total number of errors in the Module.
643 /// Result type is always `bool`.
644 /// Uses the `un_op` field.
645 /// Note that the number of errors in the Module cannot be considered stable until
646 /// flush().
647 cmp_lt_errors_len,
648
640649 pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
641650 return switch (op) {
642651 .lt => .cmp_lt,
......@@ -928,6 +937,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
928937 .cmp_gte,
929938 .cmp_gt,
930939 .cmp_neq,
940 .cmp_lt_errors_len,
931941 .is_null,
932942 .is_non_null,
933943 .is_null_ptr,
......@@ -936,9 +946,9 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
936946 .is_non_err,
937947 .is_err_ptr,
938948 .is_non_err_ptr,
939 => return Type.initTag(.bool),
949 => return Type.bool,
940950
941 .const_ty => return Type.initTag(.type),
951 .const_ty => return Type.type,
942952
943953 .alloc,
944954 .ret_ptr,
src/AstGen.zig+1-1
......@@ -7250,7 +7250,7 @@ fn builtinCall(
72507250
72517251 .ptr_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ptr_to_int),
72527252 .error_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .error_to_int),
7253 .int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type }, params[0], .int_to_error),
7253 .int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .u16_type }, params[0], .int_to_error),
72547254 .compile_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .compile_error),
72557255 .set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .u32_type }, params[0], .set_eval_branch_quota),
72567256 .enum_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .enum_to_int),
src/Liveness.zig+1
......@@ -393,6 +393,7 @@ fn analyzeInst(
393393 .ceil,
394394 .round,
395395 .trunc_float,
396 .cmp_lt_errors_len,
396397 => {
397398 const operand = inst_datas[inst].un_op;
398399 return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
src/Sema.zig+9-10
......@@ -5640,7 +5640,7 @@ fn zirErrorToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
56405640 const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
56415641 const op = sema.resolveInst(inst_data.operand);
56425642 const op_coerced = try sema.coerce(block, Type.anyerror, op, operand_src);
5643 const result_ty = Type.initTag(.u16);
5643 const result_ty = Type.u16;
56445644
56455645 if (try sema.resolveMaybeUndefVal(block, src, op_coerced)) |val| {
56465646 if (val.isUndef()) {
......@@ -5665,32 +5665,31 @@ fn zirIntToError(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
56655665 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
56665666 const src = inst_data.src();
56675667 const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
5668
5669 const op = sema.resolveInst(inst_data.operand);
5668 const uncasted_operand = sema.resolveInst(inst_data.operand);
5669 const operand = try sema.coerce(block, Type.u16, uncasted_operand, operand_src);
56705670 const target = sema.mod.getTarget();
56715671
5672 if (try sema.resolveDefinedValue(block, operand_src, op)) |value| {
5673 const int = value.toUnsignedInt(target);
5672 if (try sema.resolveDefinedValue(block, operand_src, operand)) |value| {
5673 const int = try sema.usizeCast(block, operand_src, value.toUnsignedInt(target));
56745674 if (int > sema.mod.global_error_set.count() or int == 0)
56755675 return sema.fail(block, operand_src, "integer value {d} represents no error", .{int});
56765676 const payload = try sema.arena.create(Value.Payload.Error);
56775677 payload.* = .{
56785678 .base = .{ .tag = .@"error" },
5679 .data = .{ .name = sema.mod.error_name_list.items[@intCast(usize, int)] },
5679 .data = .{ .name = sema.mod.error_name_list.items[int] },
56805680 };
56815681 return sema.addConstant(Type.anyerror, Value.initPayload(&payload.base));
56825682 }
56835683 try sema.requireRuntimeBlock(block, src);
56845684 if (block.wantSafety()) {
5685 return sema.fail(block, src, "TODO: get max errors in compilation", .{});
5686 // const is_gt_max = @panic("TODO get max errors in compilation");
5687 // try sema.addSafetyCheck(block, is_gt_max, .invalid_error_code);
5685 const is_lt_len = try block.addUnOp(.cmp_lt_errors_len, operand);
5686 try sema.addSafetyCheck(block, is_lt_len, .invalid_error_code);
56885687 }
56895688 return block.addInst(.{
56905689 .tag = .bitcast,
56915690 .data = .{ .ty_op = .{
56925691 .ty = Air.Inst.Ref.anyerror_type,
5693 .operand = op,
5692 .operand = operand,
56945693 } },
56955694 });
56965695}
src/arch/aarch64/CodeGen.zig+10
......@@ -570,7 +570,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
570570 .cmp_gte => try self.airCmp(inst, .gte),
571571 .cmp_gt => try self.airCmp(inst, .gt),
572572 .cmp_neq => try self.airCmp(inst, .neq),
573
573574 .cmp_vector => try self.airCmpVector(inst),
575 .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
574576
575577 .bool_and => try self.airBinOp(inst),
576578 .bool_or => try self.airBinOp(inst),
......@@ -2660,6 +2662,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
26602662 return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch});
26612663}
26622664
2665fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
2666 const un_op = self.air.instructions.items(.data)[inst].un_op;
2667 const operand = try self.resolveInst(un_op);
2668 _ = operand;
2669 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch});
2670 return self.finishAir(inst, result, .{ un_op, .none, .none });
2671}
2672
26632673fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
26642674 const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
26652675
src/arch/arm/CodeGen.zig+10
......@@ -567,7 +567,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
567567 .cmp_gte => try self.airCmp(inst, .gte),
568568 .cmp_gt => try self.airCmp(inst, .gt),
569569 .cmp_neq => try self.airCmp(inst, .neq),
570
570571 .cmp_vector => try self.airCmpVector(inst),
572 .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
571573
572574 .bool_and => try self.airBinOp(inst),
573575 .bool_or => try self.airBinOp(inst),
......@@ -3063,6 +3065,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
30633065 return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch});
30643066}
30653067
3068fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
3069 const un_op = self.air.instructions.items(.data)[inst].un_op;
3070 const operand = try self.resolveInst(un_op);
3071 _ = operand;
3072 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch});
3073 return self.finishAir(inst, result, .{ un_op, .none, .none });
3074}
3075
30663076fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
30673077 const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
30683078
src/arch/riscv64/CodeGen.zig+10
......@@ -537,7 +537,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
537537 .cmp_gte => try self.airCmp(inst, .gte),
538538 .cmp_gt => try self.airCmp(inst, .gt),
539539 .cmp_neq => try self.airCmp(inst, .neq),
540
540541 .cmp_vector => try self.airCmpVector(inst),
542 .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
541543
542544 .bool_and => try self.airBoolOp(inst),
543545 .bool_or => try self.airBoolOp(inst),
......@@ -1799,6 +1801,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
17991801 return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch});
18001802}
18011803
1804fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
1805 const un_op = self.air.instructions.items(.data)[inst].un_op;
1806 const operand = try self.resolveInst(un_op);
1807 _ = operand;
1808 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch});
1809 return self.finishAir(inst, result, .{ un_op, .none, .none });
1810}
1811
18021812fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
18031813 const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
18041814
src/arch/wasm/CodeGen.zig+12
......@@ -1319,7 +1319,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
13191319 .cmp_lte => self.airCmp(inst, .lte),
13201320 .cmp_lt => self.airCmp(inst, .lt),
13211321 .cmp_neq => self.airCmp(inst, .neq),
1322
13221323 .cmp_vector => self.airCmpVector(inst),
1324 .cmp_lt_errors_len => self.airCmpLtErrorsLen(inst),
13231325
13241326 .array_elem_val => self.airArrayElemVal(inst),
13251327 .array_to_slice => self.airArrayToSlice(inst),
......@@ -2267,6 +2269,16 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
22672269 return self.fail("TODO implement airCmpVector for wasm", .{});
22682270}
22692271
2272fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2273 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
2274
2275 const un_op = self.air.instructions.items(.data)[inst].un_op;
2276 const operand = try self.resolveInst(un_op);
2277
2278 _ = operand;
2279 return self.fail("TODO implement airCmpLtErrorsLen for wasm", .{});
2280}
2281
22702282fn airBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
22712283 const br = self.air.instructions.items(.data)[inst].br;
22722284 const block = self.blocks.get(br.block_inst).?;
src/arch/x86_64/CodeGen.zig+10
......@@ -693,7 +693,9 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
693693 .cmp_gte => try self.airCmp(inst, .gte),
694694 .cmp_gt => try self.airCmp(inst, .gt),
695695 .cmp_neq => try self.airCmp(inst, .neq),
696
696697 .cmp_vector => try self.airCmpVector(inst),
698 .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
697699
698700 .bool_and => try self.airBoolOp(inst),
699701 .bool_or => try self.airBoolOp(inst),
......@@ -3818,6 +3820,14 @@ fn airCmpVector(self: *Self, inst: Air.Inst.Index) !void {
38183820 return self.fail("TODO implement airCmpVector for {}", .{self.target.cpu.arch});
38193821}
38203822
3823fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void {
3824 const un_op = self.air.instructions.items(.data)[inst].un_op;
3825 const operand = try self.resolveInst(un_op);
3826 _ = operand;
3827 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airCmpLtErrorsLen for {}", .{self.target.cpu.arch});
3828 return self.finishAir(inst, result, .{ un_op, .none, .none });
3829}
3830
38213831fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void {
38223832 const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
38233833 const payload = try self.addExtra(Mir.DbgLineColumn{
src/codegen/c.zig+2-1
......@@ -1767,7 +1767,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
17671767 .cmp_eq => try airEquality(f, inst, "((", "=="),
17681768 .cmp_neq => try airEquality(f, inst, "!((", "!="),
17691769
1770 .cmp_vector => return f.fail("TODO: C backend: implement binary op for tag '{s}'", .{@tagName(Air.Inst.Tag.cmp_vector)}),
1770 .cmp_vector => return f.fail("TODO: C backend: implement cmp_vector", .{}),
1771 .cmp_lt_errors_len => return f.fail("TODO: C backend: implement cmp_lt_errors_len", .{}),
17711772
17721773 // bool_and and bool_or are non-short-circuit operations
17731774 .bool_and => try airBinOp(f, inst, " & "),
src/codegen/llvm.zig+63
......@@ -440,8 +440,38 @@ pub const Object = struct {
440440 error_name_table_ptr_global.setInitializer(error_name_table_ptr);
441441 }
442442
443 fn genCmpLtErrorsLenFunction(object: *Object, comp: *Compilation) !void {
444 // If there is no such function in the module, it means the source code does not need it.
445 const llvm_fn = object.llvm_module.getNamedFunction(lt_errors_fn_name) orelse return;
446 const mod = comp.bin_file.options.module.?;
447 const errors_len = mod.global_error_set.count();
448
449 // Delete previous implementation. We replace it with every flush() because the
450 // total number of errors may have changed.
451 while (llvm_fn.getFirstBasicBlock()) |bb| {
452 bb.deleteBasicBlock();
453 }
454
455 const builder = object.context.createBuilder();
456
457 const entry_block = object.context.appendBasicBlock(llvm_fn, "Entry");
458 builder.positionBuilderAtEnd(entry_block);
459 builder.clearCurrentDebugLocation();
460
461 // Example source of the following LLVM IR:
462 // fn __zig_lt_errors_len(index: u16) bool {
463 // return index < total_errors_len;
464 // }
465
466 const lhs = llvm_fn.getParam(0);
467 const rhs = lhs.typeOf().constInt(errors_len, .False);
468 const is_lt = builder.buildICmp(.ULT, lhs, rhs, "");
469 _ = builder.buildRet(is_lt);
470 }
471
443472 pub fn flushModule(self: *Object, comp: *Compilation) !void {
444473 try self.genErrorNameTable(comp);
474 try self.genCmpLtErrorsLenFunction(comp);
445475
446476 if (self.di_builder) |dib| {
447477 // When lowering debug info for pointers, we emitted the element types as
......@@ -3457,7 +3487,9 @@ pub const FuncGen = struct {
34573487 .cmp_lt => try self.airCmp(inst, .lt),
34583488 .cmp_lte => try self.airCmp(inst, .lte),
34593489 .cmp_neq => try self.airCmp(inst, .neq),
3490
34603491 .cmp_vector => try self.airCmpVector(inst),
3492 .cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
34613493
34623494 .is_non_null => try self.airIsNonNull(inst, false, false, .NE),
34633495 .is_non_null_ptr => try self.airIsNonNull(inst, true , false, .NE),
......@@ -3738,6 +3770,16 @@ pub const FuncGen = struct {
37383770 return self.cmp(lhs, rhs, vec_ty, cmp_op);
37393771 }
37403772
3773 fn airCmpLtErrorsLen(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
3774 if (self.liveness.isUnused(inst)) return null;
3775
3776 const un_op = self.air.instructions.items(.data)[inst].un_op;
3777 const operand = try self.resolveInst(un_op);
3778 const llvm_fn = try self.getCmpLtErrorsLenFunction();
3779 const args: [1]*const llvm.Value = .{operand};
3780 return self.builder.buildCall(llvm_fn, &args, args.len, .Fast, .Auto, "");
3781 }
3782
37413783 fn cmp(
37423784 self: *FuncGen,
37433785 lhs: *const llvm.Value,
......@@ -6392,6 +6434,25 @@ pub const FuncGen = struct {
63926434 return fn_val;
63936435 }
63946436
6437 fn getCmpLtErrorsLenFunction(self: *FuncGen) !*const llvm.Value {
6438 if (self.dg.object.llvm_module.getNamedFunction(lt_errors_fn_name)) |llvm_fn| {
6439 return llvm_fn;
6440 }
6441
6442 // Function signature: fn (anyerror) bool
6443
6444 const ret_llvm_ty = try self.dg.llvmType(Type.bool);
6445 const anyerror_llvm_ty = try self.dg.llvmType(Type.anyerror);
6446 const param_types = [_]*const llvm.Type{anyerror_llvm_ty};
6447
6448 const fn_type = llvm.functionType(ret_llvm_ty, &param_types, param_types.len, .False);
6449 const llvm_fn = self.dg.object.llvm_module.addFunction(lt_errors_fn_name, fn_type);
6450 llvm_fn.setLinkage(.Internal);
6451 llvm_fn.setFunctionCallConv(.Fast);
6452 self.dg.addCommonFnAttributes(llvm_fn);
6453 return llvm_fn;
6454 }
6455
63956456 fn airErrorName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
63966457 if (self.liveness.isUnused(inst)) return null;
63976458
......@@ -7663,3 +7724,5 @@ const AnnotatedDITypePtr = enum(usize) {
76637724 return @truncate(u1, @enumToInt(self)) != 0;
76647725 }
76657726};
7727
7728const lt_errors_fn_name = "__zig_lt_errors_len";
src/print_air.zig+1
......@@ -166,6 +166,7 @@ const Writer = struct {
166166 .ceil,
167167 .round,
168168 .trunc_float,
169 .cmp_lt_errors_len,
169170 => try w.writeUnOp(s, inst),
170171
171172 .breakpoint,
test/behavior/cast.zig+5-1
......@@ -360,7 +360,11 @@ test "expected [*c]const u8, found [*:0]const u8" {
360360}
361361
362362test "explicit cast from integer to error type" {
363 if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
363 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
364 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
365 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
366 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
367 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
364368
365369 try testCastIntToErr(error.ItBroke);
366370 comptime try testCastIntToErr(error.ItBroke);