stage2: implement safety checks at the zir_sema level
This commit is contained in:
@@ -2219,11 +2219,6 @@ pub fn wantSafety(self: *Module, scope: *Scope) bool {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn analyzeUnreach(self: *Module, scope: *Scope, src: usize) InnerError!*Inst {
|
||||
const b = try self.requireRuntimeBlock(scope, src);
|
||||
return self.addNoOp(b, src, Type.initTag(.noreturn), .unreach);
|
||||
}
|
||||
|
||||
pub fn analyzeIsNull(
|
||||
self: *Module,
|
||||
scope: *Scope,
|
||||
@@ -2902,3 +2897,70 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub const PanicId = enum {
|
||||
unreach,
|
||||
unwrap_null,
|
||||
};
|
||||
|
||||
pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic_id: PanicId) !void {
|
||||
const block_inst = try parent_block.arena.create(Inst.Block);
|
||||
block_inst.* = .{
|
||||
.base = .{
|
||||
.tag = Inst.Block.base_tag,
|
||||
.ty = Type.initTag(.void),
|
||||
.src = ok.src,
|
||||
},
|
||||
.body = .{
|
||||
.instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the condbr.
|
||||
},
|
||||
};
|
||||
|
||||
const ok_body: ir.Body = .{
|
||||
.instructions = try parent_block.arena.alloc(*Inst, 1), // Only need space for the brvoid.
|
||||
};
|
||||
const brvoid = try parent_block.arena.create(Inst.BrVoid);
|
||||
brvoid.* = .{
|
||||
.base = .{
|
||||
.tag = .brvoid,
|
||||
.ty = Type.initTag(.noreturn),
|
||||
.src = ok.src,
|
||||
},
|
||||
.block = block_inst,
|
||||
};
|
||||
ok_body.instructions[0] = &brvoid.base;
|
||||
|
||||
var fail_block: Scope.Block = .{
|
||||
.parent = parent_block,
|
||||
.func = parent_block.func,
|
||||
.decl = parent_block.decl,
|
||||
.instructions = .{},
|
||||
.arena = parent_block.arena,
|
||||
};
|
||||
defer fail_block.instructions.deinit(mod.gpa);
|
||||
|
||||
_ = try mod.safetyPanic(&fail_block, ok.src, panic_id);
|
||||
|
||||
const fail_body: ir.Body = .{ .instructions = try parent_block.arena.dupe(*Inst, fail_block.instructions.items) };
|
||||
|
||||
const condbr = try parent_block.arena.create(Inst.CondBr);
|
||||
condbr.* = .{
|
||||
.base = .{
|
||||
.tag = .condbr,
|
||||
.ty = Type.initTag(.noreturn),
|
||||
.src = ok.src,
|
||||
},
|
||||
.condition = ok,
|
||||
.then_body = ok_body,
|
||||
.else_body = fail_body,
|
||||
};
|
||||
block_inst.body.instructions[0] = &condbr.base;
|
||||
|
||||
try parent_block.instructions.append(mod.gpa, &block_inst.base);
|
||||
}
|
||||
|
||||
pub fn safetyPanic(mod: *Module, block: *Scope.Block, src: usize, panic_id: PanicId) !*Inst {
|
||||
// TODO Once we have a panic function to call, call it here instead of breakpoint.
|
||||
_ = try mod.addNoOp(block, src, Type.initTag(.void), .breakpoint);
|
||||
return mod.addNoOp(block, src, Type.initTag(.noreturn), .unreach);
|
||||
}
|
||||
|
||||
@@ -668,8 +668,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
.store => return self.genStore(inst.castTag(.store).?),
|
||||
.sub => return self.genSub(inst.castTag(.sub).?),
|
||||
.unreach => return MCValue{ .unreach = {} },
|
||||
.unwrap_optional_safe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_safe).?, true),
|
||||
.unwrap_optional_unsafe => return self.genUnwrapOptional(inst.castTag(.unwrap_optional_unsafe).?, false),
|
||||
.unwrap_optional => return self.genUnwrapOptional(inst.castTag(.unwrap_optional).?),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,7 +818,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
}
|
||||
}
|
||||
|
||||
fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp, safety_check: bool) !MCValue {
|
||||
fn genUnwrapOptional(self: *Self, inst: *ir.Inst.UnOp) !MCValue {
|
||||
// No side effects, so if it's unreferenced, do nothing.
|
||||
if (inst.base.isUnused())
|
||||
return MCValue.dead;
|
||||
|
||||
@@ -82,8 +82,7 @@ pub const Inst = struct {
|
||||
not,
|
||||
floatcast,
|
||||
intcast,
|
||||
unwrap_optional_safe,
|
||||
unwrap_optional_unsafe,
|
||||
unwrap_optional,
|
||||
|
||||
pub fn Type(tag: Tag) type {
|
||||
return switch (tag) {
|
||||
@@ -104,8 +103,7 @@ pub const Inst = struct {
|
||||
.floatcast,
|
||||
.intcast,
|
||||
.load,
|
||||
.unwrap_optional_safe,
|
||||
.unwrap_optional_unsafe,
|
||||
.unwrap_optional,
|
||||
=> UnOp,
|
||||
|
||||
.add,
|
||||
|
||||
@@ -1927,8 +1927,7 @@ const EmitZIR = struct {
|
||||
.isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull),
|
||||
.load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref),
|
||||
.ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref),
|
||||
.unwrap_optional_safe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_safe).?, .unwrap_optional_safe),
|
||||
.unwrap_optional_unsafe => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional_unsafe).?, .unwrap_optional_unsafe),
|
||||
.unwrap_optional => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional).?, .unwrap_optional_unsafe),
|
||||
|
||||
.add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add),
|
||||
.sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub),
|
||||
|
||||
@@ -68,8 +68,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
|
||||
.deref => return analyzeInstDeref(mod, scope, old_inst.castTag(.deref).?),
|
||||
.as => return analyzeInstAs(mod, scope, old_inst.castTag(.as).?),
|
||||
.@"asm" => return analyzeInstAsm(mod, scope, old_inst.castTag(.@"asm").?),
|
||||
.@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?),
|
||||
.unreach_nocheck => return analyzeInstUnreachNoChk(mod, scope, old_inst.castTag(.unreach_nocheck).?),
|
||||
.@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?, true),
|
||||
.unreach_nocheck => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.unreach_nocheck).?, false),
|
||||
.@"return" => return analyzeInstRet(mod, scope, old_inst.castTag(.@"return").?),
|
||||
.returnvoid => return analyzeInstRetVoid(mod, scope, old_inst.castTag(.returnvoid).?),
|
||||
.@"fn" => return analyzeInstFn(mod, scope, old_inst.castTag(.@"fn").?),
|
||||
@@ -313,7 +313,7 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!
|
||||
if (operand.value()) |val| {
|
||||
const ref_payload = try scope.arena().create(Value.Payload.RefVal);
|
||||
ref_payload.* = .{ .val = val };
|
||||
|
||||
|
||||
return mod.constInst(scope, inst.base.src, .{
|
||||
.ty = ptr_type,
|
||||
.val = Value.initPayload(&ref_payload.base),
|
||||
@@ -677,7 +677,7 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp
|
||||
try mod.singleMutPtrType(scope, unwrap.base.src, child_type);
|
||||
|
||||
if (operand.value()) |val| {
|
||||
if (val.tag() == .null_value) {
|
||||
if (val.isNull()) {
|
||||
return mod.fail(scope, unwrap.base.src, "unable to unwrap null", .{});
|
||||
}
|
||||
return mod.constInst(scope, unwrap.base.src, .{
|
||||
@@ -687,10 +687,11 @@ fn analyzeInstUnwrapOptional(mod: *Module, scope: *Scope, unwrap: *zir.Inst.UnOp
|
||||
}
|
||||
|
||||
const b = try mod.requireRuntimeBlock(scope, unwrap.base.src);
|
||||
return if (safety_check)
|
||||
mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_safe, operand)
|
||||
else
|
||||
mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional_unsafe, operand);
|
||||
if (safety_check and mod.wantSafety(scope)) {
|
||||
const is_non_null = try mod.addUnOp(b, unwrap.base.src, Type.initTag(.bool), .isnonnull, operand);
|
||||
try mod.addSafetyCheck(b, is_non_null, .unwrap_null);
|
||||
}
|
||||
return mod.addUnOp(b, unwrap.base.src, child_pointer, .unwrap_optional, operand);
|
||||
}
|
||||
|
||||
fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst {
|
||||
@@ -1167,18 +1168,19 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE
|
||||
return mod.addCondBr(parent_block, inst.base.src, cond, then_body, else_body);
|
||||
}
|
||||
|
||||
fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst {
|
||||
return mod.analyzeUnreach(scope, unreach.base.src);
|
||||
}
|
||||
|
||||
fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst {
|
||||
fn analyzeInstUnreachable(
|
||||
mod: *Module,
|
||||
scope: *Scope,
|
||||
unreach: *zir.Inst.NoOp,
|
||||
safety_check: bool,
|
||||
) InnerError!*Inst {
|
||||
const b = try mod.requireRuntimeBlock(scope, unreach.base.src);
|
||||
// TODO Add compile error for @optimizeFor occurring too late in a scope.
|
||||
if (mod.wantSafety(scope)) {
|
||||
// TODO Once we have a panic function to call, call it here instead of this.
|
||||
_ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint);
|
||||
if (safety_check and mod.wantSafety(scope)) {
|
||||
return mod.safetyPanic(b, unreach.base.src, .unreach);
|
||||
} else {
|
||||
return mod.addNoOp(b, unreach.base.src, Type.initTag(.noreturn), .unreach);
|
||||
}
|
||||
return mod.analyzeUnreach(scope, unreach.base.src);
|
||||
}
|
||||
|
||||
fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
|
||||
|
||||
Reference in New Issue
Block a user