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
14 changes: 2 additions & 12 deletions src/compiler/codegen/expressions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,7 @@ In Tile IR codegen, only ghost types (zero-size immutables like `Val{V}`,
"""
function emit_new!(ctx::CGCtx, expr::Expr, @nospecialize(result_type))
T = CC.widenconst(result_type)

# Ghost types: no runtime representation
if is_ghost_type(T)
if T <: Val && length(T.parameters) == 1
return ghost_value(T, T.parameters[1])
elseif T <: Constant && length(T.parameters) >= 2
return ghost_value(T, T.parameters[2])
end
return ghost_value(T)
end

is_ghost_type(T) && return ghost_value(T)
throw(IRError("Struct construction not supported in Tile IR: $T"))
end

Expand All @@ -69,7 +59,7 @@ function emit_rhs!(ctx::CGCtx, @nospecialize(rhs), @nospecialize(result_type))
elseif rhs isa QuoteNode
return emit_constant!(ctx, rhs.value, result_type)
elseif rhs isa GlobalRef
return nothing
return emit_value!(ctx, rhs)
else
return emit_constant!(ctx, rhs, result_type)
end
Expand Down
40 changes: 23 additions & 17 deletions src/compiler/codegen/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ Optionally stores a compile-time constant value.
ghost_value(@nospecialize(jltype)) = CGVal(nothing, TypeId(-1), jltype, Int[], nothing, nothing, nothing)
ghost_value(@nospecialize(jltype), constant) = CGVal(nothing, TypeId(-1), jltype, Int[], nothing, Some(constant), nothing)

"""
constant_value(jltype, type_id, constant) -> CGVal

Deferred constant: has a Tile IR type but no bytecode value yet.
Materialized (ConstantOp emitted) on demand at SSA lookup time.
Parallel to Julia's non-ghost cgval from mark_julia_const.
"""
constant_value(@nospecialize(jltype), type_id::TypeId, constant) =
CGVal(nothing, type_id, jltype, Int[], nothing, Some(constant), nothing)

"""
tuple_value(jltype, component_refs, component_constants) -> CGVal

Expand Down Expand Up @@ -278,15 +288,22 @@ function require_concrete_type(@nospecialize(T), context::String)
end

"""
tile_type_for_julia!(ctx, T) -> TypeId
tile_type_for_julia!(ctx, T; throw_error=true) -> TypeId or nothing

Get or create a Tile IR type for a Julia type.
Get or create a Tile IR type for a Julia type. With `throw_error=false`, returns
`nothing` instead of throwing if the type has no Tile IR representation.
"""
function tile_type_for_julia!(ctx::CGCtx, @nospecialize(T))
function tile_type_for_julia!(ctx::CGCtx, @nospecialize(T); throw_error::Bool=true)
actual_type = CC.widenconst(T)
get!(ctx.type_cache, actual_type) do
_tile_type_for_julia!(ctx.tt, actual_type)
cached = get(ctx.type_cache, actual_type, nothing)
cached !== nothing && return cached
type_id = _tile_type_for_julia!(ctx.tt, actual_type)
if type_id !== nothing
ctx.type_cache[actual_type] = type_id
return type_id
end
throw_error && throw(IRError("Unsupported Julia type for Tile IR: $actual_type"))
return nothing
end

function _tile_type_for_julia!(tt::TypeTable, @nospecialize(T::Type))
Expand Down Expand Up @@ -333,7 +350,7 @@ function _tile_type_for_julia!(tt::TypeTable, @nospecialize(T::Type))
return tile_type!(tt, elem_dtype, shape)
end

throw(IRError("Unsupported Julia type for Tile IR: $T"))
return nothing
end

"""
Expand Down Expand Up @@ -414,17 +431,6 @@ function extract_argument_index(@nospecialize(arg))
nothing
end

function resolve_or_constant(ctx::CGCtx, @nospecialize(arg), type_id::TypeId)
tv = emit_value!(ctx, arg)
# If we have a runtime value, use it
tv.v !== nothing && return tv.v
# Otherwise emit a constant from the compile-time value
tv.constant === nothing && throw(IRError("Cannot resolve argument"))
val = something(tv.constant)
bytes = reinterpret(UInt8, [Int32(val)])
encode_ConstantOp!(ctx.cb, type_id, collect(bytes))
end

#-----------------------------------------------------------------------------
# Tile helpers
#-----------------------------------------------------------------------------
Expand Down
57 changes: 43 additions & 14 deletions src/compiler/codegen/values.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,27 @@ Emit/resolve a value reference to a CGVal using multiple dispatch.
"""
function emit_value!(ctx::CGCtx, ssa::SSAValue)
tv = ctx[ssa]
tv !== nothing && return tv
throw(IRError("SSAValue %$(ssa.id) not found in context"))
tv !== nothing || throw(IRError("SSAValue %$(ssa.id) not found in context"))
return maybe_materialize!(ctx, ssa, tv)
end

"""
maybe_materialize!(ctx, ssa, tv) -> CGVal

Materialize a deferred constant into bytecode on demand.
Only acts on CGVals with `type_id != TypeId(-1)` and no value yet (deferred constants).
"""
function maybe_materialize!(ctx::CGCtx, ssa::SSAValue, tv::CGVal)
tv.v !== nothing && return tv # already materialized
tv.type_id == TypeId(-1) && return tv # ghost — nothing to materialize
tv.constant === nothing && return tv # no constant to materialize

val = something(tv.constant)
bytes = constant_to_bytes(val, CC.widenconst(tv.jltype))
v = encode_ConstantOp!(ctx.cb, tv.type_id, bytes)
materialized = CGVal(v, tv.type_id, tv.jltype, Int[], nothing, tv.constant, nothing)
ctx[ssa] = materialized
return materialized
end
emit_value!(ctx::CGCtx, arg::Argument) = ctx[arg]
emit_value!(ctx::CGCtx, slot::SlotNumber) = ctx[slot]
Expand Down Expand Up @@ -61,7 +80,17 @@ end

function emit_value!(ctx::CGCtx, ref::GlobalRef)
val = getfield(ref.mod, ref.name)
ghost_value(typeof(val), val)
T = typeof(val)
# Ghost types have no materializable representation.
# Non-ghost types with a Tile IR type become deferred constants (materialized on demand).
# Everything else (functions, enums, etc.) is compile-time only → ghost.
if !is_ghost_type(T)
type_id = tile_type_for_julia!(ctx, T; throw_error=false)
if type_id !== nothing
return constant_value(T, type_id, val)
end
end
ghost_value(T, val)
end

function emit_value!(ctx::CGCtx, node::PiNode)
Expand All @@ -73,7 +102,7 @@ function emit_value!(ctx::CGCtx, node::PiNode)
emit_value!(ctx, node.val)
end

emit_value!(ctx::CGCtx, ::Nothing) = nothing
emit_value!(ctx::CGCtx, ::Nothing) = ghost_value(Nothing, nothing)

"""
get_constant(ctx, ref) -> Union{Any, Nothing}
Expand All @@ -89,7 +118,14 @@ function get_constant(ctx::CGCtx, @nospecialize(ref))
end
# IR references - extract constant through emit_value!
tv = emit_value!(ctx, ref)
tv === nothing ? nothing : (tv.constant === nothing ? nothing : something(tv.constant))
tv === nothing && return nothing
if tv.constant !== nothing
return something(tv.constant)
end
# Any ghost singleton can be reconstructed from its type
T = CC.widenconst(tv.jltype)
is_ghost_type(T) && isdefined(T, :instance) && return T.instance
return nothing
end

# Symbols are compile-time only values
Expand All @@ -107,15 +143,8 @@ emit_value!(ctx::CGCtx, @nospecialize(val::Type)) = ghost_value(Type{val}, val)
# Fallback for other types (constants embedded in IR)
function emit_value!(ctx::CGCtx, @nospecialize(val))
T = typeof(val)
# Handle Val{V} instances
if T <: Val && length(T.parameters) == 1
return ghost_value(T, T.parameters[1])
end
# Handle Constant{T, V} instances
if T <: Constant && length(T.parameters) >= 2
return ghost_value(T, T.parameters[2])
end
throw(IRError("Unhandled value type in emit_value!: $(typeof(val))"))
is_ghost_type(T) && return ghost_value(T, val)
throw(IRError("Unsupported value type in Tile IR codegen: $T"))
end


Expand Down
42 changes: 31 additions & 11 deletions src/compiler/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ CC.may_optimize(::cuTileInterpreter) = true
CC.may_compress(::cuTileInterpreter) = true
CC.may_discard_trees(::cuTileInterpreter) = false

#=============================================================================
Custom return-type inference (tfuncs) for intrinsics
=============================================================================#

# Per-intrinsic return type overrides using multiple dispatch.
# Returns nothing when no override applies (fallback).
# Concrete per-intrinsic methods are defined in intrinsics/ (after the
# Intrinsics module exists).
tfunc(@nospecialize(f), argtypes::Vector{Any}) = nothing

#=============================================================================
Subprogram inference for reduce/scan
=============================================================================#
Expand Down Expand Up @@ -147,7 +157,8 @@ function _infer_subprogram(interp::cuTileInterpreter, @nospecialize(f),
end
end

# Override abstract_call_known to trigger subprogram inference for reduce/scan.
# Override abstract_call_known for custom return-type inference (tfuncs) and
# subprogram inference for reduce/scan.
#
# On 1.12+, abstract_call_known returns Future{CallMeta}. The caller uses the
# CallMeta.info to populate stmt_info[pc], which compute_edges! later walks.
Expand All @@ -161,16 +172,18 @@ end
result = @invoke CC.abstract_call_known(interp::CC.AbstractInterpreter, f::Any,
arginfo::CC.ArgInfo, si::CC.StmtInfo, vtypes::Union{CC.VarTable,Nothing},
sv::CC.InferenceState, max_methods::Int)
rt_override = tfunc(f, arginfo.argtypes)
subprog = _infer_subprogram(interp, f, arginfo, si, vtypes, sv)
subprog === nothing && return result
rt_override === nothing && subprog === nothing && return result
wrapped = CC.Future{CC.CallMeta}()
push!(sv.tasks, function (interp′, sv′)
isready(result) || return false
isready(subprog) || return false
subprog !== nothing && !isready(subprog) && return false
cm = result[]
sp = subprog[]
wrapped[] = CC.CallMeta(cm.rt, cm.exct, cm.effects,
SubprogramCallInfo(cm.info, sp.info), cm.refinements)
sp = subprog !== nothing ? subprog[] : nothing
rt = rt_override !== nothing ? rt_override : cm.rt
info = sp !== nothing ? SubprogramCallInfo(cm.info, sp.info) : cm.info
wrapped[] = CC.CallMeta(rt, cm.exct, cm.effects, info, cm.refinements)
return true
end)
return wrapped
Expand All @@ -182,16 +195,18 @@ elseif isdefined(CC, :Future) # 1.12–1.13
result = @invoke CC.abstract_call_known(interp::CC.AbstractInterpreter, f::Any,
arginfo::CC.ArgInfo, si::CC.StmtInfo,
sv::CC.InferenceState, max_methods::Int)
rt_override = tfunc(f, arginfo.argtypes)
subprog = _infer_subprogram(interp, f, arginfo, si, nothing, sv)
subprog === nothing && return result
rt_override === nothing && subprog === nothing && return result
wrapped = CC.Future{CC.CallMeta}()
push!(sv.tasks, function (interp′, sv′)
isready(result) || return false
isready(subprog) || return false
subprog !== nothing && !isready(subprog) && return false
cm = result[]
sp = subprog[]
wrapped[] = CC.CallMeta(cm.rt, cm.exct, cm.effects,
SubprogramCallInfo(cm.info, sp.info), cm.refinements)
sp = subprog !== nothing ? subprog[] : nothing
rt = rt_override !== nothing ? rt_override : cm.rt
info = sp !== nothing ? SubprogramCallInfo(cm.info, sp.info) : cm.info
wrapped[] = CC.CallMeta(rt, cm.exct, cm.effects, info, cm.refinements)
return true
end)
return wrapped
Expand All @@ -204,6 +219,11 @@ else # 1.11: synchronous, edges auto-tracked via stmt_edges
arginfo::CC.ArgInfo, si::CC.StmtInfo,
sv::CC.AbsIntState, max_methods::Int)
_infer_subprogram(interp, f, arginfo, si, nothing, sv) # side-effect only
rt_override = tfunc(f, arginfo.argtypes)
if rt_override !== nothing
return CC.CallMeta(rt_override, result.exct, result.effects,
result.info)
end
return result
end
end
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/intrinsics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ end
# Sometimes that's not possible, e.g., because the functionality required for that is
# overlayed by methods calling back into the intrinsic (e.g. `sin`), so for those
# intrinsics we disable constant folding using a `compilerbarrier(:const)`
#
# NOTE: Side-effectful intrinsics (stores, atomics) use `donotdelete(args...)` in their
# bodies to prevent the optimizer from DCE'ing calls. `donotdelete` is a Julia builtin
# with `effect_free=ALWAYS_FALSE`, which inference propagates through the function body.
# `@assume_effects !:effect_free` does NOT work — `override_effects` can only strengthen
# effects (set ALWAYS_TRUE), not weaken them. Spoofing `ipo_effects` via a custom
# `CC.finish!` override is possible but fragile (must race against `finishinfer!` setting
# `use_const_api` based on pre-override effects). `donotdelete` is the simplest correct
# approach.

emit_intrinsic!(ctx::CGCtx, @nospecialize(func), args) = missing

Expand Down
Loading