diff --git a/Compiler/src/ssair/slot2ssa.jl b/Compiler/src/ssair/slot2ssa.jl index d9ea2539f0ffc..16a964b4d72f1 100644 --- a/Compiler/src/ssair/slot2ssa.jl +++ b/Compiler/src/ssair/slot2ssa.jl @@ -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 diff --git a/base/boot.jl b/base/boot.jl index 02b1e83739335..f1ef8bd37a276 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -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) @@ -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 diff --git a/base/essentials.jl b/base/essentials.jl index fec8ee89f9128..820cce6839f13 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -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} diff --git a/base/expr.jl b/base/expr.jl index bf9e2ef2bf92c..6a684ea30d1c2 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -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) @@ -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) @@ -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) diff --git a/base/hashing.jl b/base/hashing.jl index 848b69fee7e23..1b323f2e9097e 100644 --- a/base/hashing.jl +++ b/base/hashing.jl @@ -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) diff --git a/base/public.jl b/base/public.jl index 3308c58ff1780..217d91b615848 100644 --- a/base/public.jl +++ b/base/public.jl @@ -68,6 +68,11 @@ public ispublic, remove_linenums!, +# AST handling + IR, + isa_ast_node, + quoted, + # Operators operator_associativity, operator_precedence, diff --git a/base/timing.jl b/base/timing.jl index 9e3a4cf128413..998103d1e78bc 100644 --- a/base/timing.jl +++ b/base/timing.jl @@ -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 diff --git a/doc/src/devdocs/builtins.md b/doc/src/devdocs/builtins.md index bb6b1ae0d82a2..2f1651b2b2518 100644 --- a/doc/src/devdocs/builtins.md +++ b/doc/src/devdocs/builtins.md @@ -41,4 +41,6 @@ Core.get_binding_type Core.IntrinsicFunction Core.Intrinsics Core.IR +Base.quoted +Base.isa_ast_node ``` diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index 9ed579594571a..4fbc291fc1901 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -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 diff --git a/src/ast.c b/src/ast.c index fdb29349a0db6..04c87d220f409 100644 --- a/src/ast.c +++ b/src/ast.c @@ -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) @@ -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 || @@ -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) diff --git a/test/core.jl b/test/core.jl index c84d68bafece9..4abe4f95a2e0b 100644 --- a/test/core.jl +++ b/test/core.jl @@ -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 diff --git a/test/hashing.jl b/test/hashing.jl index 4e05b8a0102eb..1af73e45fc541 100644 --- a/test/hashing.jl +++ b/test/hashing.jl @@ -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)),