Skip to content

Commit e4600c5

Browse files
authored
Relax constraints on the PHI block (JuliaLang#50308)
In JuliaLang#50158, I tought the verifier to reject code that has invalid statements in the original PHI block. In JuliaLang#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 JuliaLang#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 7eb358e commit e4600c5

File tree

3 files changed

+43
-17
lines changed

3 files changed

+43
-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 | IR_FLAG_NOTHROW)) == IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
165+
if is_inlineable_constant(rt.val) && (ir.stmts[idx][:flag] & (IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW)) == IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
166166
ir.stmts[idx][:inst] = quoted(rt.val)
167167
end
168168
return true

base/compiler/ssair/verify.jl

Lines changed: 17 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,12 @@ function verify_ir(ir::IRCode, print::Bool=true,
189190
end
190191
lastbb = 0
191192
is_phinode_block = false
193+
firstidx = 1
194+
lastphi = 1
192195
for (bb, idx) in bbidxiter(ir)
193196
if bb != lastbb
194197
is_phinode_block = true
198+
lastphi = firstidx = idx
195199
lastbb = bb
196200
end
197201
# We allow invalid IR in dead code to avoid passes having to detect when
@@ -204,6 +208,7 @@ function verify_ir(ir::IRCode, print::Bool=true,
204208
@verify_error "φ node $idx is not at the beginning of the basic block $bb"
205209
error("")
206210
end
211+
lastphi = idx
207212
@assert length(stmt.edges) == length(stmt.values)
208213
for i = 1:length(stmt.edges)
209214
edge = stmt.edges[i]
@@ -244,12 +249,19 @@ function verify_ir(ir::IRCode, print::Bool=true,
244249
check_op(ir, domtree, val, Int(edge), last(ir.cfg.blocks[stmt.edges[i]].stmts)+1, idx, print, false, i, allow_frontend_forms)
245250
end
246251
continue
247-
elseif stmt === nothing
248-
# Nothing to do
249-
continue
250252
end
251253

252-
is_phinode_block = false
254+
if is_phinode_block && isa(stmt, Union{Expr, UpsilonNode, PhiCNode, SSAValue})
255+
if !isa(stmt, Expr) || !is_value_pos_expr_head(stmt.head)
256+
# Go back and check that all non-PhiNodes are valid value-position
257+
for validate_idx in firstidx:(lastphi-1)
258+
validate_stmt = ir.stmts[validate_idx][:inst]
259+
isa(validate_stmt, PhiNode) && continue
260+
check_op(ir, domtree, validate_stmt, bb, idx, idx, print, false, 0, allow_frontend_forms)
261+
end
262+
is_phinode_block = false
263+
end
264+
end
253265
if isa(stmt, PhiCNode)
254266
for i = 1:length(stmt.values)
255267
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)