Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Compiler/src/ssair/slot2ssa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ end
SlotInfo() = SlotInfo(Int[], Int[], false)

function scan_entry!(result::Vector{SlotInfo}, idx::Int, @nospecialize(stmt))
# NewVarNodes count as defs for the purpose
# NewvarNodes count as defs for the purpose
# of liveness analysis (i.e. they kill use chains)
if isa(stmt, NewvarNode)
result[slot_id(stmt.slot)].any_newvar = true
Expand Down
7 changes: 5 additions & 2 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,7 @@ const undef = UndefInitializer()
# empty vector constructor
(self::Type{GenericMemory{kind,T,addrspace}})() where {T,kind,addrspace} = self(undef, 0)

# memoryref is simply convenience wrapper function around memoryrefnew
memoryref(mem::GenericMemory) = memoryrefnew(mem)
memoryref(mem::GenericMemory, i::Integer) = memoryrefnew(memoryrefnew(mem), Int(i), @_boundscheck)
memoryref(ref::GenericMemoryRef, i::Integer) = memoryrefnew(ref, Int(i), @_boundscheck)
Expand Down Expand Up @@ -744,17 +745,19 @@ let
end

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

export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode,
NewvarNode, SSAValue, SlotNumber, Argument,
PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo,
Const, PartialStruct, InterConditional, EnterNode, memoryref
Const, PartialStruct, InterConditional, EnterNode

using Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode,
NewvarNode, SSAValue, SlotNumber, Argument,
PiNode, PhiNode, PhiCNode, UpsilonNode, DebugInfo,
Const, PartialStruct, InterConditional, EnterNode, memoryref
Const, PartialStruct, InterConditional, EnterNode

end # module IR

Expand Down
2 changes: 1 addition & 1 deletion base/essentials.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

using Core: CodeInfo, SimpleVector, donotdelete, compilerbarrier, memoryrefnew, memoryrefget, memoryrefset!
using Core: CodeInfo, SimpleVector, donotdelete, compilerbarrier, memoryref, memoryrefnew, memoryrefget, memoryrefset!

const Callable = Union{Function,Type}

Expand Down
130 changes: 119 additions & 11 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ function copy(x::PhiCNode)
return PhiCNode(new_values)
end

# copy parts of an AST that the compiler mutates
# copy parts of an IR that the compiler mutates
# (this is not a general-purpose copy for an Expr AST)
function copy_exprs(@nospecialize(x))
if isa(x, Expr)
return copy(x)
Expand Down Expand Up @@ -91,10 +92,86 @@ function copy(c::CodeInfo)
return cnew
end

function isequal_exprarg(@nospecialize(x), @nospecialize(y))
x isa typeof(y) || return false
x === y && return true
# c.f. list of types in copy_expr also
if x isa Expr
x == (y::Expr) && return true
elseif x isa QuoteNode
x == (y::QuoteNode) && return true
elseif x isa PhiNode
x == (y::PhiNode) && return true
elseif x isa PhiCNode
x == (y::PhiCNode) && return true
elseif x isa CodeInfo
x == (y::CodeInfo) && return true
end
return false
end


function isequal_exprargs(x::Array{Any,1}, y::Array{Any,1})
l = length(x)
l == length(y) || return false
for i = 1:l
if !isassigned(x, i)
# phi and phic values are permitted to be undef
isassigned(y, i) && return false
else
isassigned(y, i) || return false
isequal_exprarg(x[i], y[i]) || return false
end
end
return true
end

# define == such that == inputs to parsing (including line numbers) yield == outputs from lowering (including all metadata)
# (aside from cases where parsing just returns a number, which are ambiguous here)
==(x::Expr, y::Expr) = x.head === y.head && isequal_exprargs(x.args, y.args)

==(x::QuoteNode, y::QuoteNode) = isequal_exprarg(x.value, y.value)

==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = isequal(stmt1.edges, stmt2.edges) && isequal_exprargs(stmt1.values, stmt2.values)

==(stmt1::Core.PhiCNode, stmt2::Core.PhiCNode) = isequal_exprargs(stmt1.values, stmt2.values)

function ==(stmt1::CodeInfo, stmt2::CodeInfo)
for i in 1:nfields(stmt1)
if !isdefined(stmt1, i)
isdefined(stmt2, i) && return false
else
isdefined(stmt2, i) || return false
f1 = getfield(stmt1, i)
f2 = getfield(stmt2, i)
f1 isa typeof(f2) || return false
if f1 isa Vector{Any}
# code or types vectors
isequal_exprargs(f1, f2::Vector{Any}) || return false
elseif f1 isa DebugInfo
f1 == f2::DebugInfo || return false
elseif f1 isa Vector
# misc data
l = length(f1)
l == length(f2::Vector) || return false
for i = 1:l
f1[i] === f2[i] || return false
end
else
# misc fields
f1 === f2 || return false
end
end
end
return true
end

==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args)
==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value)
==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = stmt1.edges == stmt2.edges && stmt1.values == stmt2.values
function ==(x::DebugInfo, y::DebugInfo)
for i in 1:nfields(x)
getfield(x, i) == getfield(y, i) || return false
end
return true
end

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

function is_self_quoting(@nospecialize(x))
return isa(x,Number) || isa(x,AbstractString) || isa(x,Tuple) || isa(x,Type) ||
isa(x,Char) || x === nothing || isa(x,Function)
end
"""
isa_ast_node(x)

function quoted(@nospecialize(x))
return is_self_quoting(x) ? x : QuoteNode(x)
end
Return false if `x` is not interpreted specially by any of inference, lowering,
or codegen as either an AST or IR special form.
"""
function isa_ast_node(@nospecialize x)
# c.f. Core.IR module, augmented with AST types
return x isa NewvarNode ||
x isa CodeInfo ||
x isa LineNumberNode ||
x isa GotoNode ||
x isa GotoIfNot ||
x isa EnterNode ||
x isa ReturnNode ||
x isa SSAValue ||
x isa SlotNumber ||
x isa Argument ||
x isa QuoteNode ||
x isa GlobalRef ||
x isa Symbol ||
x isa PiNode ||
x isa PhiNode ||
x isa PhiCNode ||
x isa UpsilonNode ||
x isa Expr
end

is_self_quoting(@nospecialize(x)) = !isa_ast_node(x)

"""
quoted(x)

Return `x` made safe for inserting as a constant into IR. Note that this does
not make it safe for inserting into an AST, since eval will sometimes copy some
types of AST object inside, and even may sometimes evaluate and interpolate any
`\$` inside, depending on the context.
"""
quoted(@nospecialize(x)) = isa_ast_node(x) ? QuoteNode(x) : x

# Implementation of generated functions
function generated_body_to_codeinfo(ex::Expr, defmod::Module, isva::Bool)
Expand Down
21 changes: 21 additions & 0 deletions base/hashing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,32 @@ end

## symbol & expression hashing ##
if UInt === UInt64
# conservatively hash using == equality of all of the data, even though == often uses === internally
hash(x::Expr, h::UInt) = hash(x.args, hash(x.head, h ⊻ 0x83c7900696d26dc6))
hash(x::QuoteNode, h::UInt) = hash(x.value, h ⊻ 0x2c97bf8b3de87020)
hash(x::PhiNode, h::UInt) = hash(x.edges, hash(x.values, h ⊻ 0x2c97bf8b3de87020))
hash(x::PhiCNode, h::UInt) = hash(x.values, h ⊻ 0x2c97bf8b3de87020)
else
hash(x::Expr, h::UInt) = hash(x.args, hash(x.head, h ⊻ 0x469d72af))
hash(x::QuoteNode, h::UInt) = hash(x.value, h ⊻ 0x469d72af)
hash(x::PhiNode, h::UInt) = hash(x.edges, hash(x.values, h ⊻ 0x469d72af))
hash(x::PhiCNode, h::UInt) = hash(x.values, h ⊻ 0x469d72af)
end

function hash(x::CodeInfo, h::UInt)
h ⊻= UInt === UInt64 ? 0x2c97bf8b3de87020 : 0x469d72af
for i in 1:nfields(x)
h = hash(isdefined(x, i) ? getfield(x, i) : missing, h)
end
return h
end

function hash(x::DebugInfo, h::UInt)
h ⊻= UInt === UInt64 ? 0x2c97bf8b3de87020 : 0x469d72af
for i in 1:nfields(x)
h = hash(getfield(x, i), h)
end
return h
end

hash(x::Symbol) = objectid(x)
Expand Down
5 changes: 5 additions & 0 deletions base/public.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public
ispublic,
remove_linenums!,

# AST handling
IR,
isa_ast_node,
quoted,

# Operators
operator_associativity,
operator_precedence,
Expand Down
2 changes: 1 addition & 1 deletion base/timing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ function is_simply_call(@nospecialize ex)
for a in ex.args
a isa QuoteNode && continue
a isa Symbol && continue
Base.is_self_quoting(a) && continue
isa_ast_node(a) || continue
return false
end
return true
Expand Down
2 changes: 2 additions & 0 deletions doc/src/devdocs/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ Core.get_binding_type
Core.IntrinsicFunction
Core.Intrinsics
Core.IR
Base.quoted
Base.isa_ast_node
```
8 changes: 8 additions & 0 deletions doc/src/manual/metaprogramming.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,14 @@ QuoteNode

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

Note that while it does not support `$`, it also does not prevent it, nor does
it preserve the identity of the wrapped object:

```jldoctest
julia> b = 2; eval(Expr(:quote, QuoteNode(Expr(:$, :b))))
:($(QuoteNode(2)))
```

### Evaluating expressions

Given an expression object, one can cause Julia to evaluate (execute) it at global scope using
Expand Down
13 changes: 8 additions & 5 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1047,14 +1047,15 @@ int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT

// Utility function to return whether `e` is any of the special AST types or
// will always evaluate to itself exactly unchanged. This corresponds to
// `is_self_quoting` in Core.Compiler utilities.
int jl_is_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
// `isa_ast_node` in Core.Compiler utilities.
int jl_isa_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
{
return jl_is_newvarnode(e)
|| jl_is_code_info(e)
|| jl_is_linenode(e)
|| jl_is_gotonode(e)
|| jl_is_gotoifnot(e)
|| jl_is_enternode(e)
|| jl_is_returnnode(e)
|| jl_is_ssavalue(e)
|| jl_is_slotnumber(e)
Expand All @@ -1069,9 +1070,10 @@ int jl_is_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
|| jl_is_expr(e);
}

static int is_self_quoting_expr(jl_expr_t *e) JL_NOTSAFEPOINT
static int is_self_escaping_expr(jl_expr_t *e) JL_NOTSAFEPOINT
{
return (e->head == jl_inert_sym ||
e->head == jl_leave_sym ||
e->head == jl_core_sym ||
e->head == jl_line_sym ||
e->head == jl_lineinfo_sym ||
Expand All @@ -1089,12 +1091,13 @@ int need_esc_node(jl_value_t *e) JL_NOTSAFEPOINT
|| jl_is_ssavalue(e)
|| jl_is_slotnumber(e)
|| jl_is_argument(e)
|| jl_is_enternode(e)
|| jl_is_quotenode(e))
return 0;
if (jl_is_expr(e))
return !is_self_quoting_expr((jl_expr_t*)e);
return !is_self_escaping_expr((jl_expr_t*)e);
// note: jl_is_globalref(e) is not included here, since we care a little about about having a line number for it
return jl_is_ast_node(e);
return jl_isa_ast_node(e);
}

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)
Expand Down
24 changes: 22 additions & 2 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4679,8 +4679,28 @@ end
@test Macro_Yielding_Global_Assignment.x == 2

# issue #15718
@test :(f($NaN)) == :(f($NaN))
@test isequal(:(f($NaN)), :(f($NaN)))
function compare_test(x, y)
lx = Meta.lower(@__MODULE__, x)
ly = Meta.lower(@__MODULE__, y)
if isequal(x, y)
@test x == y
@test hash(x) == hash(y)
@test isequal(lx, ly)
@test lx == ly
@test hash(lx) == hash(ly)
true
else
@test x != y
@test !isequal(lx, ly)
@test lx != ly
false
end
end
@test compare_test(:(f($NaN)), :(f($NaN)))
@test !compare_test(:(1 + (1 * 1)), :(1 + (1 * 1.0)))
@test compare_test(:(1 + (1 * $NaN)), :(1 + (1 * $NaN)))
@test compare_test(QuoteNode(NaN), QuoteNode(NaN))
@test !compare_test(QuoteNode(1), QuoteNode(1.0))

# PR #16011 Make sure dead code elimination doesn't delete push and pop
# of metadata
Expand Down
8 changes: 7 additions & 1 deletion test/hashing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,14 @@ end
@test hash([1,2]) == hash(view([1,2,3,4],1:2))

let a = QuoteNode(1), b = QuoteNode(1.0)
@test (hash(a)==hash(b)) == (a==b)
@test hash(a) == hash(b)
@test a != b
end
let a = QuoteNode(:(1 + 2)), b = QuoteNode(:(1 + 2))
@test hash(a) == hash(b)
@test a == b
end


let a = Expr(:block, Core.SlotNumber(1)),
b = Expr(:block, Core.SlotNumber(1)),
Expand Down