Skip to content

Commit 4e85692

Browse files
committed
refine IR model queries
- `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 some sort of expression object (such as `QuoteNode(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 66ec6ee commit 4e85692

File tree

11 files changed

+193
-23
lines changed

11 files changed

+193
-23
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)