Skip to content

Commit 098a4f8

Browse files
authored
refine IR model queries (#58661)
- `jl_isa_ast_node` was missing `enter`/`leave` nodes. - `Core.IR` exports mistakenly included a function `memoryref`. - `Base.IR`, and `quoted` were not public or documented. - Add julia function `isa_ast_node` to improve accuracy of `quoted`. - Change `==` on AST nodes to check egal equality of any constants in the IR / AST, and make hashing consistent with that change. This helpfully allows determining that `x + 1` and `x + 1.0` are not equivalent, exchangeable operations. If you need to compare any two objects for semantic equality, you may need to first wrap them with `x = Base.isa_ast_node(x) ? x : QuoteNode(x)` to resolve the ambiguity of whether the comparison is of the semantics or value. - Handle `undef` fields in Phi/PhiC node equality and hashing
1 parent 58e20a1 commit 098a4f8

File tree

12 files changed

+200
-24
lines changed

12 files changed

+200
-24
lines changed

Compiler/src/ssair/slot2ssa.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ end
88
SlotInfo() = SlotInfo(Int[], Int[], false)
99

1010
function scan_entry!(result::Vector{SlotInfo}, idx::Int, @nospecialize(stmt))
11-
# NewVarNodes count as defs for the purpose
11+
# NewvarNodes count as defs for the purpose
1212
# of liveness analysis (i.e. they kill use chains)
1313
if isa(stmt, NewvarNode)
1414
result[slot_id(stmt.slot)].any_newvar = true

base/boot.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ const undef = UndefInitializer()
590590
# empty vector constructor
591591
(self::Type{GenericMemory{kind,T,addrspace}})() where {T,kind,addrspace} = self(undef, 0)
592592

593+
# memoryref is simply convenience wrapper function around memoryrefnew
593594
memoryref(mem::GenericMemory) = memoryrefnew(mem)
594595
memoryref(mem::GenericMemory, i::Integer) = memoryrefnew(memoryrefnew(mem), Int(i), @_boundscheck)
595596
memoryref(ref::GenericMemoryRef, i::Integer) = memoryrefnew(ref, Int(i), @_boundscheck)
@@ -744,17 +745,19 @@ let
744745
end
745746

746747
# module providing the IR object model
748+
# excluding types already exported by Core (GlobalRef, QuoteNode, Expr, LineNumberNode)
749+
# any type beyond these is self-quoting (see also Base.is_ast_node)
747750
module IR
748751

749752
export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode,
750753
NewvarNode, SSAValue, SlotNumber, Argument,
751754
PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo,
752-
Const, PartialStruct, InterConditional, EnterNode, memoryref
755+
Const, PartialStruct, InterConditional, EnterNode
753756

754757
using Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode,
755758
NewvarNode, SSAValue, SlotNumber, Argument,
756759
PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo,
757-
Const, PartialStruct, InterConditional, EnterNode, memoryref
760+
Const, PartialStruct, InterConditional, EnterNode
758761

759762
end # module IR
760763

base/essentials.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
using Core: CodeInfo, SimpleVector, donotdelete, compilerbarrier, memoryrefnew, memoryrefget, memoryrefset!
3+
using Core: CodeInfo, SimpleVector, donotdelete, compilerbarrier, memoryref, memoryrefnew, memoryrefget, memoryrefset!
44

55
const Callable = Union{Function,Type}
66

base/expr.jl

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ function copy(x::PhiCNode)
6060
return PhiCNode(new_values)
6161
end
6262

63-
# copy parts of an AST that the compiler mutates
63+
# copy parts of an IR that the compiler mutates
64+
# (this is not a general-purpose copy for an Expr AST)
6465
function copy_exprs(@nospecialize(x))
6566
if isa(x, Expr)
6667
return copy(x)
@@ -91,10 +92,86 @@ function copy(c::CodeInfo)
9192
return cnew
9293
end
9394

95+
function isequal_exprarg(@nospecialize(x), @nospecialize(y))
96+
x isa typeof(y) || return false
97+
x === y && return true
98+
# c.f. list of types in copy_expr also
99+
if x isa Expr
100+
x == (y::Expr) && return true
101+
elseif x isa QuoteNode
102+
x == (y::QuoteNode) && return true
103+
elseif x isa PhiNode
104+
x == (y::PhiNode) && return true
105+
elseif x isa PhiCNode
106+
x == (y::PhiCNode) && return true
107+
elseif x isa CodeInfo
108+
x == (y::CodeInfo) && return true
109+
end
110+
return false
111+
end
112+
113+
114+
function isequal_exprargs(x::Array{Any,1}, y::Array{Any,1})
115+
l = length(x)
116+
l == length(y) || return false
117+
for i = 1:l
118+
if !isassigned(x, i)
119+
# phi and phic values are permitted to be undef
120+
isassigned(y, i) && return false
121+
else
122+
isassigned(y, i) || return false
123+
isequal_exprarg(x[i], y[i]) || return false
124+
end
125+
end
126+
return true
127+
end
128+
129+
# define == such that == inputs to parsing (including line numbers) yield == outputs from lowering (including all metadata)
130+
# (aside from cases where parsing just returns a number, which are ambiguous here)
131+
==(x::Expr, y::Expr) = x.head === y.head && isequal_exprargs(x.args, y.args)
132+
133+
==(x::QuoteNode, y::QuoteNode) = isequal_exprarg(x.value, y.value)
134+
135+
==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = isequal(stmt1.edges, stmt2.edges) && isequal_exprargs(stmt1.values, stmt2.values)
136+
137+
==(stmt1::Core.PhiCNode, stmt2::Core.PhiCNode) = isequal_exprargs(stmt1.values, stmt2.values)
138+
139+
function ==(stmt1::CodeInfo, stmt2::CodeInfo)
140+
for i in 1:nfields(stmt1)
141+
if !isdefined(stmt1, i)
142+
isdefined(stmt2, i) && return false
143+
else
144+
isdefined(stmt2, i) || return false
145+
f1 = getfield(stmt1, i)
146+
f2 = getfield(stmt2, i)
147+
f1 isa typeof(f2) || return false
148+
if f1 isa Vector{Any}
149+
# code or types vectors
150+
isequal_exprargs(f1, f2::Vector{Any}) || return false
151+
elseif f1 isa DebugInfo
152+
f1 == f2::DebugInfo || return false
153+
elseif f1 isa Vector
154+
# misc data
155+
l = length(f1)
156+
l == length(f2::Vector) || return false
157+
for i = 1:l
158+
f1[i] === f2[i] || return false
159+
end
160+
else
161+
# misc fields
162+
f1 === f2 || return false
163+
end
164+
end
165+
end
166+
return true
167+
end
94168

95-
==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args)
96-
==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value)
97-
==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = stmt1.edges == stmt2.edges && stmt1.values == stmt2.values
169+
function ==(x::DebugInfo, y::DebugInfo)
170+
for i in 1:nfields(x)
171+
getfield(x, i) == getfield(y, i) || return false
172+
end
173+
return true
174+
end
98175

99176
"""
100177
macroexpand(m::Module, x; recursive=true)
@@ -1662,14 +1739,45 @@ end
16621739
is_meta_expr_head(head::Symbol) = head === :boundscheck || head === :meta || head === :loopinfo
16631740
is_meta_expr(@nospecialize x) = isa(x, Expr) && is_meta_expr_head(x.head)
16641741

1665-
function is_self_quoting(@nospecialize(x))
1666-
return isa(x,Number) || isa(x,AbstractString) || isa(x,Tuple) || isa(x,Type) ||
1667-
isa(x,Char) || x === nothing || isa(x,Function)
1668-
end
1742+
"""
1743+
isa_ast_node(x)
16691744
1670-
function quoted(@nospecialize(x))
1671-
return is_self_quoting(x) ? x : QuoteNode(x)
1672-
end
1745+
Return false if `x` is not interpreted specially by any of inference, lowering,
1746+
or codegen as either an AST or IR special form.
1747+
"""
1748+
function isa_ast_node(@nospecialize x)
1749+
# c.f. Core.IR module, augmented with AST types
1750+
return x isa NewvarNode ||
1751+
x isa CodeInfo ||
1752+
x isa LineNumberNode ||
1753+
x isa GotoNode ||
1754+
x isa GotoIfNot ||
1755+
x isa EnterNode ||
1756+
x isa ReturnNode ||
1757+
x isa SSAValue ||
1758+
x isa SlotNumber ||
1759+
x isa Argument ||
1760+
x isa QuoteNode ||
1761+
x isa GlobalRef ||
1762+
x isa Symbol ||
1763+
x isa PiNode ||
1764+
x isa PhiNode ||
1765+
x isa PhiCNode ||
1766+
x isa UpsilonNode ||
1767+
x isa Expr
1768+
end
1769+
1770+
is_self_quoting(@nospecialize(x)) = !isa_ast_node(x)
1771+
1772+
"""
1773+
quoted(x)
1774+
1775+
Return `x` made safe for inserting as a constant into IR. Note that this does
1776+
not make it safe for inserting into an AST, since eval will sometimes copy some
1777+
types of AST object inside, and even may sometimes evaluate and interpolate any
1778+
`\$` inside, depending on the context.
1779+
"""
1780+
quoted(@nospecialize(x)) = isa_ast_node(x) ? QuoteNode(x) : x
16731781

16741782
# Implementation of generated functions
16751783
function generated_body_to_codeinfo(ex::Expr, defmod::Module, isva::Bool)

base/hashing.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,32 @@ end
232232

233233
## symbol & expression hashing ##
234234
if UInt === UInt64
235+
# conservatively hash using == equality of all of the data, even though == often uses === internally
235236
hash(x::Expr, h::UInt) = hash(x.args, hash(x.head, h 0x83c7900696d26dc6))
236237
hash(x::QuoteNode, h::UInt) = hash(x.value, h 0x2c97bf8b3de87020)
238+
hash(x::PhiNode, h::UInt) = hash(x.edges, hash(x.values, h 0x2c97bf8b3de87020))
239+
hash(x::PhiCNode, h::UInt) = hash(x.values, h 0x2c97bf8b3de87020)
237240
else
238241
hash(x::Expr, h::UInt) = hash(x.args, hash(x.head, h 0x469d72af))
239242
hash(x::QuoteNode, h::UInt) = hash(x.value, h 0x469d72af)
243+
hash(x::PhiNode, h::UInt) = hash(x.edges, hash(x.values, h 0x469d72af))
244+
hash(x::PhiCNode, h::UInt) = hash(x.values, h 0x469d72af)
245+
end
246+
247+
function hash(x::CodeInfo, h::UInt)
248+
h ⊻= UInt === UInt64 ? 0x2c97bf8b3de87020 : 0x469d72af
249+
for i in 1:nfields(x)
250+
h = hash(isdefined(x, i) ? getfield(x, i) : missing, h)
251+
end
252+
return h
253+
end
254+
255+
function hash(x::DebugInfo, h::UInt)
256+
h ⊻= UInt === UInt64 ? 0x2c97bf8b3de87020 : 0x469d72af
257+
for i in 1:nfields(x)
258+
h = hash(getfield(x, i), h)
259+
end
260+
return h
240261
end
241262

242263
hash(x::Symbol) = objectid(x)

base/public.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ public
6868
ispublic,
6969
remove_linenums!,
7070

71+
# AST handling
72+
IR,
73+
isa_ast_node,
74+
quoted,
75+
7176
# Operators
7277
operator_associativity,
7378
operator_precedence,

base/timing.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ function is_simply_call(@nospecialize ex)
495495
for a in ex.args
496496
a isa QuoteNode && continue
497497
a isa Symbol && continue
498-
Base.is_self_quoting(a) && continue
498+
isa_ast_node(a) || continue
499499
return false
500500
end
501501
return true

doc/src/devdocs/builtins.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@ Core.get_binding_type
4141
Core.IntrinsicFunction
4242
Core.Intrinsics
4343
Core.IR
44+
Base.quoted
45+
Base.isa_ast_node
4446
```

doc/src/manual/metaprogramming.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,14 @@ QuoteNode
363363

364364
`QuoteNode` can also be used for certain advanced metaprogramming tasks.
365365

366+
Note that while it does not support `$`, it also does not prevent it, nor does
367+
it preserve the identity of the wrapped object:
368+
369+
```jldoctest
370+
julia> b = 2; eval(Expr(:quote, QuoteNode(Expr(:$, :b))))
371+
:($(QuoteNode(2)))
372+
```
373+
366374
### Evaluating expressions
367375

368376
Given an expression object, one can cause Julia to evaluate (execute) it at global scope using

src/ast.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,14 +1047,15 @@ int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT
10471047

10481048
// Utility function to return whether `e` is any of the special AST types or
10491049
// will always evaluate to itself exactly unchanged. This corresponds to
1050-
// `is_self_quoting` in Core.Compiler utilities.
1051-
int jl_is_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
1050+
// `isa_ast_node` in Core.Compiler utilities.
1051+
int jl_isa_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
10521052
{
10531053
return jl_is_newvarnode(e)
10541054
|| jl_is_code_info(e)
10551055
|| jl_is_linenode(e)
10561056
|| jl_is_gotonode(e)
10571057
|| jl_is_gotoifnot(e)
1058+
|| jl_is_enternode(e)
10581059
|| jl_is_returnnode(e)
10591060
|| jl_is_ssavalue(e)
10601061
|| jl_is_slotnumber(e)
@@ -1069,9 +1070,10 @@ int jl_is_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
10691070
|| jl_is_expr(e);
10701071
}
10711072

1072-
static int is_self_quoting_expr(jl_expr_t *e) JL_NOTSAFEPOINT
1073+
static int is_self_escaping_expr(jl_expr_t *e) JL_NOTSAFEPOINT
10731074
{
10741075
return (e->head == jl_inert_sym ||
1076+
e->head == jl_leave_sym ||
10751077
e->head == jl_core_sym ||
10761078
e->head == jl_line_sym ||
10771079
e->head == jl_lineinfo_sym ||
@@ -1089,12 +1091,13 @@ int need_esc_node(jl_value_t *e) JL_NOTSAFEPOINT
10891091
|| jl_is_ssavalue(e)
10901092
|| jl_is_slotnumber(e)
10911093
|| jl_is_argument(e)
1094+
|| jl_is_enternode(e)
10921095
|| jl_is_quotenode(e))
10931096
return 0;
10941097
if (jl_is_expr(e))
1095-
return !is_self_quoting_expr((jl_expr_t*)e);
1098+
return !is_self_escaping_expr((jl_expr_t*)e);
10961099
// note: jl_is_globalref(e) is not included here, since we care a little about about having a line number for it
1097-
return jl_is_ast_node(e);
1100+
return jl_isa_ast_node(e);
10981101
}
10991102

11001103
static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule, jl_module_t **ctx, jl_value_t **lineinfo, size_t world, int throw_load_error)

0 commit comments

Comments
 (0)