Skip to content

Commit 04fec61

Browse files
committed
Relax constraints on the PHI block
In #50158, I tought the verifier to reject code that has invalid statements in the original PHI block. In #50235, this required irinterp to stop folding PhiNodes to the respective constants. I said at the time that a subsequent compact would fix it, but it turns out that we don't actually have the logic for that. I might still add that logic, but on the other hand it just seems kinda silly that PhiNodes need to be a special case here. This PR relaxes the semantics of the PHI block, to allow any value-position constant to appear in the PHI block and undoes the irinterp change from #50235. Only the interpreter really cares about the semantics of the phi block, so the primary change is there. Of note, SSAValue forwards are not allowed in the phi block. This is because of the following: ``` loop: %1 = %(...) %2 = %1 %3 = %(top => %1) ``` The two phi values %1 and %2 have different semantics: %1 gets the *current* iteration of the loop, while %3 gets the *previous* value. As a result, any pass that wants to move SSAValues out of PhiNode uses would have to be aware of these semantics anyway, and there's no simplicitly benefits to allowing SSAValues in the middle of a phi block.
1 parent 0e147eb commit 04fec61

File tree

3 files changed

+41
-17
lines changed

3 files changed

+41
-17
lines changed

base/compiler/ssair/irinterp.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union
162162
if rt !== nothing
163163
if isa(rt, Const)
164164
ir.stmts[idx][:type] = rt
165-
if is_inlineable_constant(rt.val) && !isa(inst, PhiNode) && (ir.stmts[idx][:flag] & IR_FLAG_EFFECT_FREE) != 0
165+
if is_inlineable_constant(rt.val) && (ir.stmts[idx][:flag] & IR_FLAG_EFFECT_FREE) != 0
166166
ir.stmts[idx][:inst] = quoted(rt.val)
167167
end
168168
return true

base/compiler/ssair/verify.jl

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ if !isdefined(@__MODULE__, Symbol("@verify_error"))
2020
end
2121
end
2222

23+
is_value_pos_expr_head(head::Symbol) = head === :boundscheck
2324
function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, use_idx::Int, printed_use_idx::Int, print::Bool, isforeigncall::Bool, arg_idx::Int, allow_frontend_forms::Bool)
2425
if isa(op, SSAValue)
2526
if op.id > length(ir.stmts)
@@ -60,7 +61,7 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int,
6061
# Allow a tuple in symbol position for foreigncall - this isn't actually
6162
# a real call - it's interpreted in global scope by codegen. However,
6263
# we do need to keep this a real use, because it could also be a pointer.
63-
elseif op.head !== :boundscheck
64+
elseif !is_value_pos_expr_head(op.head)
6465
if !allow_frontend_forms || op.head !== :opaque_closure_method
6566
@verify_error "Expr not allowed in value position"
6667
error("")
@@ -189,9 +190,11 @@ function verify_ir(ir::IRCode, print::Bool=true,
189190
end
190191
lastbb = 0
191192
is_phinode_block = false
193+
firstidx = 1
192194
for (bb, idx) in bbidxiter(ir)
193195
if bb != lastbb
194196
is_phinode_block = true
197+
firstidx = idx
195198
lastbb = bb
196199
end
197200
# We allow invalid IR in dead code to avoid passes having to detect when
@@ -244,12 +247,19 @@ function verify_ir(ir::IRCode, print::Bool=true,
244247
check_op(ir, domtree, val, Int(edge), last(ir.cfg.blocks[stmt.edges[i]].stmts)+1, idx, print, false, i, allow_frontend_forms)
245248
end
246249
continue
247-
elseif stmt === nothing
248-
# Nothing to do
249-
continue
250250
end
251251

252-
is_phinode_block = false
252+
if is_phinode_block && isa(stmt, Union{Expr, UpsilonNode, PhiCNode, SSAValue})
253+
if !isa(stmt, Expr) || !is_value_pos_expr_head(stmt.head)
254+
# Go back and check that all non-PhiNodes are valid value-position
255+
for validate_idx in firstidx:(idx-1)
256+
validate_stmt = ir.stmts[validate_idx][:inst]
257+
isa(validate_stmt, PhiNode) && continue
258+
check_op(ir, domtree, op, validate_stmt, idx, idx, print, isforeigncall, 0, allow_frontend_forms)
259+
end
260+
is_phinode_block = false
261+
end
262+
end
253263
if isa(stmt, PhiCNode)
254264
for i = 1:length(stmt.values)
255265
val = stmt.values[i]

src/interpreter.c

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -349,20 +349,34 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_
349349
{
350350
size_t from = s->ip;
351351
size_t ip = to;
352-
unsigned nphi = 0;
352+
unsigned nphiblockstmts = 0;
353353
for (ip = to; ip < ns; ip++) {
354354
jl_value_t *e = jl_array_ptr_ref(stmts, ip);
355-
if (!jl_is_phinode(e))
356-
break;
357-
nphi += 1;
355+
if (!jl_is_phinode(e)) {
356+
if (jl_is_expr(e) || jl_is_returnnode(e) || jl_is_gotoifnot(e) ||
357+
jl_is_gotonode(e) || jl_is_phicnode(e) || jl_is_upsilonnode(e) ||
358+
jl_is_ssavalue(e)) {
359+
break;
360+
}
361+
// Everything else is allowed in the phi-block for implementation
362+
// convenience - fall through.
363+
}
364+
nphiblockstmts += 1;
358365
}
359-
if (nphi) {
366+
if (nphiblockstmts) {
360367
jl_value_t **dest = &s->locals[jl_source_nslots(s->src) + to];
361-
jl_value_t **phis; // = (jl_value_t**)alloca(sizeof(jl_value_t*) * nphi);
362-
JL_GC_PUSHARGS(phis, nphi);
363-
for (unsigned i = 0; i < nphi; i++) {
368+
jl_value_t **phis; // = (jl_value_t**)alloca(sizeof(jl_value_t*) * nphiblockstmts);
369+
JL_GC_PUSHARGS(phis, nphiblockstmts);
370+
for (unsigned i = 0; i < nphiblockstmts; i++) {
364371
jl_value_t *e = jl_array_ptr_ref(stmts, to + i);
365-
assert(jl_is_phinode(e));
372+
if (!jl_is_phinode(e)) {
373+
// IR verification guarantees that the only thing that gets
374+
// evaluated here are constants, so it doesn't matter if we
375+
// update the locals or the phis, but let's be consistent
376+
// for simplicity.
377+
phis[i] = eval_value(e, s);
378+
continue;
379+
}
366380
jl_array_t *edges = (jl_array_t*)jl_fieldref_noalloc(e, 0);
367381
ssize_t edge = -1;
368382
size_t closest = to; // implicit edge has `to <= edge - 1 < to + i`
@@ -405,7 +419,7 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_
405419
i -= n_oldphi;
406420
dest += n_oldphi;
407421
to += n_oldphi;
408-
nphi -= n_oldphi;
422+
nphiblockstmts -= n_oldphi;
409423
}
410424
if (edge != -1) {
411425
// if edges list doesn't contain last branch, or the value is explicitly undefined
@@ -418,7 +432,7 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_
418432
phis[i] = val;
419433
}
420434
// now move all phi values to their position in edges
421-
for (unsigned j = 0; j < nphi; j++) {
435+
for (unsigned j = 0; j < nphiblockstmts; j++) {
422436
dest[j] = phis[j];
423437
}
424438
JL_GC_POP();

0 commit comments

Comments
 (0)