compiler: start moving safety-checks into backends
This actually used to be how it worked in stage1, and there was this issue to change it: #2649 So this commit is a reversal to that idea. One motivation for that issue was avoiding emitting the panic handler in compilations that do not have any calls to panic. This commit only resolves the panic handler in the event of a safety check function being emitted, so it does not have that flaw. The other reason given in that issue was for optimizations that elide safety checks. It's yet to be determined whether that was a good idea or not; this can get re-explored when we start adding optimization passes to AIR. This commit adds these AIR instructions, which are only emitted if `backendSupportsFeature(.safety_checked_arithmetic)` is true: * add_safe * sub_safe * mul_safe It removes these nonsensical AIR instructions: * addwrap_optimized * subwrap_optimized * mulwrap_optimized The safety-checked arithmetic functions push the burden of invoking the panic handler into the backend. This makes for a messier compiler implementation, but it reduces the amount of AIR instructions emitted by Sema, which reduces time spent in the secondary bottleneck of the compiler. It also generates more compact LLVM IR, reducing time spent in the primary bottleneck of the compiler. Finally, it eliminates 1 stack allocation per safety-check which was being used to store the resulting tuple. These allocations were going to be annoying when combined with suspension points.
This commit is contained in:
@@ -2860,9 +2860,9 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
|
||||
.div_floor => try airBinBuiltinCall(f, inst, "div_floor", .none),
|
||||
.mod => try airBinBuiltinCall(f, inst, "mod", .none),
|
||||
|
||||
.addwrap => try airBinBuiltinCall(f, inst, "addw", .bits),
|
||||
.subwrap => try airBinBuiltinCall(f, inst, "subw", .bits),
|
||||
.mulwrap => try airBinBuiltinCall(f, inst, "mulw", .bits),
|
||||
.add_wrap => try airBinBuiltinCall(f, inst, "addw", .bits),
|
||||
.sub_wrap => try airBinBuiltinCall(f, inst, "subw", .bits),
|
||||
.mul_wrap => try airBinBuiltinCall(f, inst, "mulw", .bits),
|
||||
|
||||
.add_sat => try airBinBuiltinCall(f, inst, "adds", .bits),
|
||||
.sub_sat => try airBinBuiltinCall(f, inst, "subs", .bits),
|
||||
@@ -3048,11 +3048,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
|
||||
.wasm_memory_grow => try airWasmMemoryGrow(f, inst),
|
||||
|
||||
.add_optimized,
|
||||
.addwrap_optimized,
|
||||
.sub_optimized,
|
||||
.subwrap_optimized,
|
||||
.mul_optimized,
|
||||
.mulwrap_optimized,
|
||||
.div_float_optimized,
|
||||
.div_trunc_optimized,
|
||||
.div_floor_optimized,
|
||||
@@ -3071,6 +3068,11 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
|
||||
.int_from_float_optimized,
|
||||
=> return f.fail("TODO implement optimized float mode", .{}),
|
||||
|
||||
.add_safe,
|
||||
.sub_safe,
|
||||
.mul_safe,
|
||||
=> return f.fail("TODO implement safety_checked_instructions", .{}),
|
||||
|
||||
.is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}),
|
||||
.error_set_has_value => return f.fail("TODO: C backend: implement error_set_has_value", .{}),
|
||||
.vector_store_elem => return f.fail("TODO: C backend: implement vector_store_elem", .{}),
|
||||
|
||||
@@ -377,6 +377,9 @@ pub const Object = struct {
|
||||
/// name collision.
|
||||
extern_collisions: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, void),
|
||||
|
||||
/// Memoizes a null `?usize` value.
|
||||
null_opt_addr: ?*llvm.Value,
|
||||
|
||||
pub const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, *llvm.Type);
|
||||
|
||||
/// This is an ArrayHashMap as opposed to a HashMap because in `flushModule` we
|
||||
@@ -532,6 +535,7 @@ pub const Object = struct {
|
||||
.di_type_map = .{},
|
||||
.error_name_table = null,
|
||||
.extern_collisions = .{},
|
||||
.null_opt_addr = null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2416,6 +2420,35 @@ pub const Object = struct {
|
||||
return buffer.toOwnedSliceSentinel(0);
|
||||
}
|
||||
|
||||
fn getNullOptAddr(o: *Object) !*llvm.Value {
|
||||
if (o.null_opt_addr) |global| return global;
|
||||
|
||||
const mod = o.module;
|
||||
const target = mod.getTarget();
|
||||
const ty = try mod.intern(.{ .opt_type = .usize_type });
|
||||
const null_opt_usize = try mod.intern(.{ .opt = .{
|
||||
.ty = ty,
|
||||
.val = .none,
|
||||
} });
|
||||
|
||||
const llvm_init = try o.lowerValue(.{
|
||||
.ty = ty.toType(),
|
||||
.val = null_opt_usize.toValue(),
|
||||
});
|
||||
const global = o.llvm_module.addGlobalInAddressSpace(
|
||||
llvm_init.typeOf(),
|
||||
"",
|
||||
toLlvmGlobalAddressSpace(.generic, target),
|
||||
);
|
||||
global.setLinkage(.Internal);
|
||||
global.setUnnamedAddr(.True);
|
||||
global.setAlignment(ty.toType().abiAlignment(mod));
|
||||
global.setInitializer(llvm_init);
|
||||
|
||||
o.null_opt_addr = global;
|
||||
return global;
|
||||
}
|
||||
|
||||
/// If the llvm function does not exist, create it.
|
||||
/// Note that this can be called before the function's semantic analysis has
|
||||
/// completed, so if any attributes rely on that, they must be done in updateFunc, not here.
|
||||
@@ -3141,8 +3174,8 @@ pub const Object = struct {
|
||||
.func => |func| mod.funcPtr(func.index).owner_decl,
|
||||
else => unreachable,
|
||||
};
|
||||
const fn_decl = o.module.declPtr(fn_decl_index);
|
||||
try o.module.markDeclAlive(fn_decl);
|
||||
const fn_decl = mod.declPtr(fn_decl_index);
|
||||
try mod.markDeclAlive(fn_decl);
|
||||
return o.resolveLlvmFunction(fn_decl_index);
|
||||
},
|
||||
.int => {
|
||||
@@ -3682,11 +3715,12 @@ pub const Object = struct {
|
||||
}
|
||||
|
||||
fn lowerIntAsPtr(o: *Object, val: Value) Error!*llvm.Value {
|
||||
switch (o.module.intern_pool.indexToKey(val.toIntern())) {
|
||||
const mod = o.module;
|
||||
switch (mod.intern_pool.indexToKey(val.toIntern())) {
|
||||
.undef => return o.context.pointerType(0).getUndef(),
|
||||
.int => {
|
||||
var bigint_space: Value.BigIntSpace = undefined;
|
||||
const bigint = val.toBigInt(&bigint_space, o.module);
|
||||
const bigint = val.toBigInt(&bigint_space, mod);
|
||||
const llvm_int = lowerBigInt(o, Type.usize, bigint);
|
||||
return llvm_int.constIntToPtr(o.context.pointerType(0));
|
||||
},
|
||||
@@ -4306,15 +4340,25 @@ pub const FuncGen = struct {
|
||||
|
||||
const opt_value: ?*llvm.Value = switch (air_tags[inst]) {
|
||||
// zig fmt: off
|
||||
.add => try self.airAdd(inst, false),
|
||||
.addwrap => try self.airAddWrap(inst, false),
|
||||
.add_sat => try self.airAddSat(inst),
|
||||
.sub => try self.airSub(inst, false),
|
||||
.subwrap => try self.airSubWrap(inst, false),
|
||||
.sub_sat => try self.airSubSat(inst),
|
||||
.mul => try self.airMul(inst, false),
|
||||
.mulwrap => try self.airMulWrap(inst, false),
|
||||
.mul_sat => try self.airMulSat(inst),
|
||||
.add => try self.airAdd(inst, false),
|
||||
.add_optimized => try self.airAdd(inst, true),
|
||||
.add_wrap => try self.airAddWrap(inst),
|
||||
.add_sat => try self.airAddSat(inst),
|
||||
|
||||
.sub => try self.airSub(inst, false),
|
||||
.sub_optimized => try self.airSub(inst, true),
|
||||
.sub_wrap => try self.airSubWrap(inst),
|
||||
.sub_sat => try self.airSubSat(inst),
|
||||
|
||||
.mul => try self.airMul(inst, false),
|
||||
.mul_optimized => try self.airMul(inst, true),
|
||||
.mul_wrap => try self.airMulWrap(inst),
|
||||
.mul_sat => try self.airMulSat(inst),
|
||||
|
||||
.add_safe => try self.airSafeArithmetic(inst, "llvm.sadd.with.overflow", "llvm.uadd.with.overflow"),
|
||||
.sub_safe => try self.airSafeArithmetic(inst, "llvm.ssub.with.overflow", "llvm.usub.with.overflow"),
|
||||
.mul_safe => try self.airSafeArithmetic(inst, "llvm.smul.with.overflow", "llvm.umul.with.overflow"),
|
||||
|
||||
.div_float => try self.airDivFloat(inst, false),
|
||||
.div_trunc => try self.airDivTrunc(inst, false),
|
||||
.div_floor => try self.airDivFloor(inst, false),
|
||||
@@ -4331,12 +4375,6 @@ pub const FuncGen = struct {
|
||||
.slice => try self.airSlice(inst),
|
||||
.mul_add => try self.airMulAdd(inst),
|
||||
|
||||
.add_optimized => try self.airAdd(inst, true),
|
||||
.addwrap_optimized => try self.airAddWrap(inst, true),
|
||||
.sub_optimized => try self.airSub(inst, true),
|
||||
.subwrap_optimized => try self.airSubWrap(inst, true),
|
||||
.mul_optimized => try self.airMul(inst, true),
|
||||
.mulwrap_optimized => try self.airMulWrap(inst, true),
|
||||
.div_float_optimized => try self.airDivFloat(inst, true),
|
||||
.div_trunc_optimized => try self.airDivTrunc(inst, true),
|
||||
.div_floor_optimized => try self.airDivFloor(inst, true),
|
||||
@@ -4859,6 +4897,48 @@ pub const FuncGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn buildSimplePanic(fg: *FuncGen, panic_id: Module.PanicId) !void {
|
||||
const o = fg.dg.object;
|
||||
const mod = o.module;
|
||||
const msg_decl_index = mod.panic_messages[@intFromEnum(panic_id)].unwrap().?;
|
||||
const msg_decl = mod.declPtr(msg_decl_index);
|
||||
const msg_len = msg_decl.ty.childType(mod).arrayLen(mod);
|
||||
const msg_ptr = try o.lowerValue(.{
|
||||
.ty = msg_decl.ty,
|
||||
.val = msg_decl.val,
|
||||
});
|
||||
const null_opt_addr_global = try o.getNullOptAddr();
|
||||
const target = mod.getTarget();
|
||||
const llvm_usize = fg.context.intType(target.ptrBitWidth());
|
||||
// example:
|
||||
// call fastcc void @test2.panic(
|
||||
// ptr @builtin.panic_messages.integer_overflow__anon_987, ; msg.ptr
|
||||
// i64 16, ; msg.len
|
||||
// ptr null, ; stack trace
|
||||
// ptr @2, ; addr (null ?usize)
|
||||
// )
|
||||
const args = [4]*llvm.Value{
|
||||
msg_ptr,
|
||||
llvm_usize.constInt(msg_len, .False),
|
||||
fg.context.pointerType(0).constNull(),
|
||||
null_opt_addr_global,
|
||||
};
|
||||
const panic_func = mod.funcPtrUnwrap(mod.panic_func_index).?;
|
||||
const panic_decl = mod.declPtr(panic_func.owner_decl);
|
||||
const fn_info = mod.typeToFunc(panic_decl.ty).?;
|
||||
const panic_global = try o.resolveLlvmFunction(panic_func.owner_decl);
|
||||
_ = fg.builder.buildCall(
|
||||
try o.lowerType(panic_decl.ty),
|
||||
panic_global,
|
||||
&args,
|
||||
args.len,
|
||||
toLlvmCallConv(fn_info.cc, target),
|
||||
.Auto,
|
||||
"",
|
||||
);
|
||||
_ = fg.builder.buildUnreachable();
|
||||
}
|
||||
|
||||
fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
||||
const o = self.dg.object;
|
||||
const mod = o.module;
|
||||
@@ -6945,9 +7025,55 @@ pub const FuncGen = struct {
|
||||
return self.builder.buildNUWAdd(lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
||||
self.builder.setFastMath(want_fast_math);
|
||||
fn airSafeArithmetic(
|
||||
fg: *FuncGen,
|
||||
inst: Air.Inst.Index,
|
||||
signed_intrinsic: []const u8,
|
||||
unsigned_intrinsic: []const u8,
|
||||
) !?*llvm.Value {
|
||||
const o = fg.dg.object;
|
||||
const mod = o.module;
|
||||
|
||||
const bin_op = fg.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = try fg.resolveInst(bin_op.lhs);
|
||||
const rhs = try fg.resolveInst(bin_op.rhs);
|
||||
const inst_ty = fg.typeOfIndex(inst);
|
||||
const scalar_ty = inst_ty.scalarType(mod);
|
||||
const is_scalar = scalar_ty.ip_index == inst_ty.ip_index;
|
||||
|
||||
const intrinsic_name = switch (scalar_ty.isSignedInt(mod)) {
|
||||
true => signed_intrinsic,
|
||||
false => unsigned_intrinsic,
|
||||
};
|
||||
const llvm_inst_ty = try o.lowerType(inst_ty);
|
||||
const llvm_fn = fg.getIntrinsic(intrinsic_name, &.{llvm_inst_ty});
|
||||
const result_struct = fg.builder.buildCall(
|
||||
llvm_fn.globalGetValueType(),
|
||||
llvm_fn,
|
||||
&[_]*llvm.Value{ lhs, rhs },
|
||||
2,
|
||||
.Fast,
|
||||
.Auto,
|
||||
"",
|
||||
);
|
||||
const overflow_bit = fg.builder.buildExtractValue(result_struct, 1, "");
|
||||
const scalar_overflow_bit = switch (is_scalar) {
|
||||
true => overflow_bit,
|
||||
false => fg.builder.buildOrReduce(overflow_bit),
|
||||
};
|
||||
|
||||
const fail_block = fg.context.appendBasicBlock(fg.llvm_func, "OverflowFail");
|
||||
const ok_block = fg.context.appendBasicBlock(fg.llvm_func, "OverflowOk");
|
||||
_ = fg.builder.buildCondBr(scalar_overflow_bit, fail_block, ok_block);
|
||||
|
||||
fg.builder.positionBuilderAtEnd(fail_block);
|
||||
try fg.buildSimplePanic(.integer_overflow);
|
||||
|
||||
fg.builder.positionBuilderAtEnd(ok_block);
|
||||
return fg.builder.buildExtractValue(result_struct, 0, "");
|
||||
}
|
||||
|
||||
fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = try self.resolveInst(bin_op.lhs);
|
||||
const rhs = try self.resolveInst(bin_op.rhs);
|
||||
@@ -6986,9 +7112,7 @@ pub const FuncGen = struct {
|
||||
return self.builder.buildNUWSub(lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
||||
self.builder.setFastMath(want_fast_math);
|
||||
|
||||
fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = try self.resolveInst(bin_op.lhs);
|
||||
const rhs = try self.resolveInst(bin_op.rhs);
|
||||
@@ -7026,9 +7150,7 @@ pub const FuncGen = struct {
|
||||
return self.builder.buildNUWMul(lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
||||
self.builder.setFastMath(want_fast_math);
|
||||
|
||||
fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = try self.resolveInst(bin_op.lhs);
|
||||
const rhs = try self.resolveInst(bin_op.rhs);
|
||||
|
||||
@@ -1703,9 +1703,9 @@ pub const DeclGen = struct {
|
||||
const air_tags = self.air.instructions.items(.tag);
|
||||
const maybe_result_id: ?IdRef = switch (air_tags[inst]) {
|
||||
// zig fmt: off
|
||||
.add, .addwrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true),
|
||||
.sub, .subwrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true),
|
||||
.mul, .mulwrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true),
|
||||
.add, .add_wrap => try self.airArithOp(inst, .OpFAdd, .OpIAdd, .OpIAdd, true),
|
||||
.sub, .sub_wrap => try self.airArithOp(inst, .OpFSub, .OpISub, .OpISub, true),
|
||||
.mul, .mul_wrap => try self.airArithOp(inst, .OpFMul, .OpIMul, .OpIMul, true),
|
||||
|
||||
.div_float,
|
||||
.div_float_optimized,
|
||||
|
||||
Reference in New Issue
Block a user