Merge pull request #7552 from Vexu/stage2-continue

stage2 continue
This commit is contained in:
Veikka Tuominen 2020-12-26 12:26:16 +02:00 committed by GitHub
commit 641bf4c46e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 237 additions and 46 deletions

View File

@ -796,11 +796,15 @@ pub const Scope = struct {
/// The first N instructions in a function body ZIR are arg instructions.
instructions: std.ArrayListUnmanaged(*zir.Inst) = .{},
label: ?Label = null,
break_block: ?*zir.Inst.Block = null,
continue_block: ?*zir.Inst.Block = null,
/// only valid if label != null or (continue_block and break_block) != null
break_result_loc: astgen.ResultLoc = undefined,
pub const Label = struct {
token: ast.TokenIndex,
block_inst: *zir.Inst.Block,
result_loc: astgen.ResultLoc,
used: bool = false,
};
};

View File

@ -261,6 +261,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
.Block => return rlWrapVoid(mod, scope, rl, node, try blockExpr(mod, scope, node.castTag(.Block).?)),
.LabeledBlock => return labeledBlockExpr(mod, scope, rl, node.castTag(.LabeledBlock).?, .block),
.Break => return rlWrap(mod, scope, rl, try breakExpr(mod, scope, node.castTag(.Break).?)),
.Continue => return rlWrap(mod, scope, rl, try continueExpr(mod, scope, node.castTag(.Continue).?)),
.PtrType => return rlWrap(mod, scope, rl, try ptrType(mod, scope, node.castTag(.PtrType).?)),
.GroupedExpression => return expr(mod, scope, rl, node.castTag(.GroupedExpression).?.expr),
.ArrayType => return rlWrap(mod, scope, rl, try arrayType(mod, scope, node.castTag(.ArrayType).?)),
@ -291,7 +292,6 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
.StructInitializer => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializer", .{}),
.StructInitializerDot => return mod.failNode(scope, node, "TODO implement astgen.expr for .StructInitializerDot", .{}),
.Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}),
.Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}),
.AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}),
.FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}),
.ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}),
@ -339,48 +339,99 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr
const tree = parent_scope.tree();
const src = tree.token_locs[node.ltoken].start;
if (node.getLabel()) |break_label| {
// Look for the label in the scope.
var scope = parent_scope;
while (true) {
switch (scope.tag) {
.gen_zir => {
const gen_zir = scope.cast(Scope.GenZIR).?;
if (gen_zir.label) |label| {
if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) {
if (node.getRHS()) |rhs| {
// Most result location types can be forwarded directly; however
// if we need to write to a pointer which has an inferred type,
// proper type inference requires peer type resolution on the block's
// break operand expressions.
const branch_rl: ResultLoc = switch (label.result_loc) {
.discard, .none, .ty, .ptr, .ref => label.result_loc,
.inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = label.block_inst },
};
const operand = try expr(mod, parent_scope, branch_rl, rhs);
return try addZIRInst(mod, scope, src, zir.Inst.Break, .{
.block = label.block_inst,
.operand = operand,
}, .{});
} else {
return try addZIRInst(mod, scope, src, zir.Inst.BreakVoid, .{
.block = label.block_inst,
}, .{});
// Look for the label in the scope.
var scope = parent_scope;
while (true) {
switch (scope.tag) {
.gen_zir => {
const gen_zir = scope.cast(Scope.GenZIR).?;
const block_inst = blk: {
if (node.getLabel()) |break_label| {
if (gen_zir.label) |*label| {
if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) {
label.used = true;
break :blk label.block_inst;
}
}
} else if (gen_zir.break_block) |inst| {
break :blk inst;
}
scope = gen_zir.parent;
},
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
else => {
const label_name = try identifierTokenString(mod, parent_scope, break_label);
return mod.failTok(parent_scope, break_label, "label not found: '{}'", .{label_name});
},
}
continue;
};
if (node.getRHS()) |rhs| {
// Most result location types can be forwarded directly; however
// if we need to write to a pointer which has an inferred type,
// proper type inference requires peer type resolution on the block's
// break operand expressions.
const branch_rl: ResultLoc = switch (gen_zir.break_result_loc) {
.discard, .none, .ty, .ptr, .ref => gen_zir.break_result_loc,
.inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block_inst },
};
const operand = try expr(mod, parent_scope, branch_rl, rhs);
return try addZIRInst(mod, parent_scope, src, zir.Inst.Break, .{
.block = block_inst,
.operand = operand,
}, .{});
} else {
return try addZIRInst(mod, parent_scope, src, zir.Inst.BreakVoid, .{
.block = block_inst,
}, .{});
}
},
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
else => if (node.getLabel()) |break_label| {
const label_name = try identifierTokenString(mod, parent_scope, break_label);
return mod.failTok(parent_scope, break_label, "label not found: '{}'", .{label_name});
} else {
return mod.failTok(parent_scope, src, "break expression outside loop", .{});
},
}
}
}
fn continueExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
const tree = parent_scope.tree();
const src = tree.token_locs[node.ltoken].start;
// Look for the label in the scope.
var scope = parent_scope;
while (true) {
switch (scope.tag) {
.gen_zir => {
const gen_zir = scope.cast(Scope.GenZIR).?;
const continue_block = gen_zir.continue_block orelse {
scope = gen_zir.parent;
continue;
};
if (node.getLabel()) |break_label| blk: {
if (gen_zir.label) |*label| {
if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) {
label.used = true;
break :blk;
}
}
// found continue but either it has a different label, or no label
scope = gen_zir.parent;
continue;
}
return addZIRInst(mod, parent_scope, src, zir.Inst.BreakVoid, .{
.block = continue_block,
}, .{});
},
.local_val => scope = scope.cast(Scope.LocalVal).?.parent,
.local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,
else => if (node.getLabel()) |break_label| {
const label_name = try identifierTokenString(mod, parent_scope, break_label);
return mod.failTok(parent_scope, break_label, "label not found: '{}'", .{label_name});
} else {
return mod.failTok(parent_scope, src, "continue expression outside loop", .{});
},
}
} else {
return mod.failNode(parent_scope, &node.base, "TODO implement break from loop", .{});
}
}
@ -426,16 +477,19 @@ fn labeledBlockExpr(
.decl = parent_scope.decl().?,
.arena = gen_zir.arena,
.instructions = .{},
.break_result_loc = rl,
// TODO @as here is working around a stage1 miscompilation bug :(
.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
.token = block_node.label,
.block_inst = block_inst,
.result_loc = rl,
}),
};
defer block_scope.instructions.deinit(mod.gpa);
try blockExprStmts(mod, &block_scope.base, &block_node.base, block_node.statements());
if (!block_scope.label.?.used) {
return mod.fail(parent_scope, tree.token_locs[block_node.label].start, "unused block label", .{});
}
block_inst.positionals.body.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items);
try gen_zir.instructions.append(mod.gpa, &block_inst.base);
@ -1289,9 +1343,6 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
}
}
if (while_node.label) |tok|
return mod.failTok(scope, tok, "TODO labeled while", .{});
if (while_node.inline_token) |tok|
return mod.failTok(scope, tok, "TODO inline while", .{});
@ -1308,6 +1359,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
.decl = expr_scope.decl,
.arena = expr_scope.arena,
.instructions = .{},
.break_result_loc = rl,
};
defer loop_scope.instructions.deinit(mod.gpa);
@ -1348,6 +1400,14 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
const while_block = try addZIRInstBlock(mod, scope, while_src, .block, .{
.instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items),
});
loop_scope.break_block = while_block;
loop_scope.continue_block = cond_block;
if (while_node.label) |some| {
loop_scope.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
.token = some,
.block_inst = while_block,
});
}
const then_src = tree.token_locs[while_node.body.lastToken()].start;
var then_scope: Scope.GenZIR = .{
@ -1410,13 +1470,15 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W
condbr.positionals.else_body = .{
.instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
};
if (loop_scope.label) |some| {
if (!some.used) {
return mod.fail(scope, tree.token_locs[some.token].start, "unused while label", .{});
}
}
return &while_block.base;
}
fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) InnerError!*zir.Inst {
if (for_node.label) |tok|
return mod.failTok(scope, tok, "TODO labeled for", .{});
if (for_node.inline_token) |tok|
return mod.failTok(scope, tok, "TODO inline for", .{});
@ -1458,6 +1520,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
.decl = for_scope.decl,
.arena = for_scope.arena,
.instructions = .{},
.break_result_loc = rl,
};
defer loop_scope.instructions.deinit(mod.gpa);
@ -1499,6 +1562,14 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
const for_block = try addZIRInstBlock(mod, scope, for_src, .block, .{
.instructions = try for_scope.arena.dupe(*zir.Inst, for_scope.instructions.items),
});
loop_scope.break_block = for_block;
loop_scope.continue_block = cond_block;
if (for_node.label) |some| {
loop_scope.label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{
.token = some,
.block_inst = for_block,
});
}
// while body
const then_src = tree.token_locs[for_node.body.lastToken()].start;
@ -1585,6 +1656,11 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For)
condbr.positionals.else_body = .{
.instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
};
if (loop_scope.label) |some| {
if (!some.used) {
return mod.fail(scope, tree.token_locs[some.token].start, "unused for label", .{});
}
}
return &for_block.base;
}

View File

@ -1219,4 +1219,115 @@ pub fn addCases(ctx: *TestContext) !void {
\\extern var foo;
, &[_][]const u8{":4:1: error: unable to infer variable type"});
}
{
var case = ctx.exe("break/continue", linux_x64);
// Break out of loop
case.addCompareOutput(
\\export fn _start() noreturn {
\\ while (true) {
\\ break;
\\ }
\\
\\ exit();
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"",
);
case.addCompareOutput(
\\export fn _start() noreturn {
\\ foo: while (true) {
\\ break :foo;
\\ }
\\
\\ exit();
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"",
);
// Continue in loop
case.addCompareOutput(
\\export fn _start() noreturn {
\\ var i: u64 = 0;
\\ while (true) : (i+=1) {
\\ if (i == 4) exit();
\\ continue;
\\ }
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"",
);
case.addCompareOutput(
\\export fn _start() noreturn {
\\ var i: u64 = 0;
\\ foo: while (true) : (i+=1) {
\\ if (i == 4) exit();
\\ continue :foo;
\\ }
\\}
\\
\\fn exit() noreturn {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ : "rcx", "r11", "memory"
\\ );
\\ unreachable;
\\}
,
"",
);
}
{
var case = ctx.exe("unused labels", linux_x64);
case.addError(
\\comptime {
\\ foo: {}
\\}
, &[_][]const u8{":2:5: error: unused block label"});
case.addError(
\\comptime {
\\ foo: while (true) {}
\\}
, &[_][]const u8{":2:5: error: unused while label"});
case.addError(
\\comptime {
\\ foo: for ("foo") |_| {}
\\}
, &[_][]const u8{":2:5: error: unused for label"});
}
}