// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
pub fn ssa_full_descriptor() -> @ir.HotPassDescriptor {
// Full SSA builds an operand-expanded graph. The shared cached CFG is
// root-only, so preparing that cache would duplicate the graph work.
@ir.HotPassDescriptor::new("ssa", invalidates=[
@ir.HotAnalysis::cfg(),
@ir.HotAnalysis::dominance(),
@ir.HotAnalysis::liveness(),
@ir.HotAnalysis::use_def(),
@ir.HotAnalysis::effects(),
@ir.HotAnalysis::loop_info(),
@ir.HotAnalysis::ssa(),
])
}
///|
pub fn ssa_full_summary() -> String {
"SSA-ify local traffic with Binaryen-style fresh definitions, explicit defaults, merge locals, incoming tees, and parameter-entry prepends."
}
///|
fn ssa_full_raw_has_call_in_try_table(
instrs : Array[@lib.Instruction],
inside_try : Bool,
) -> Bool {
for instr in instrs {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => if inside_try { return true }
@lib.TryTable(_, _, @lib.Expr(body)) =>
if ssa_full_raw_has_call_in_try_table(body, true) {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if ssa_full_raw_has_call_in_try_table(body, inside_try) {
return true
}
@lib.If(_, then_body, else_body) => {
if ssa_full_raw_has_call_in_try_table(then_body, inside_try) {
return true
}
match else_body {
Some(body) =>
if ssa_full_raw_has_call_in_try_table(body, inside_try) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if ssa_full_raw_has_call_in_try_table(body, inside_try) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
if ssa_full_raw_has_call_in_try_table(catch_body, inside_try) {
return true
}
}
}
_ => ()
}
}
false
}
///|
pub(all) enum SsaFullWriteDecision {
Freshen(@ir.LocalId)
KeepCanonical
} derive(Eq, Debug)
///|
pub(all) struct SsaFullWriteRewrite {
write_id : @ir.NodeId
original_local : @ir.LocalId
decision : SsaFullWriteDecision
} derive(Eq, Debug)
///|
pub(all) enum SsaFullGetDecision {
Retarget(@ir.LocalId)
MaterializeDefault
Merge(@ir.LocalId)
KeepCanonical
} derive(Eq, Debug)
///|
pub(all) struct SsaFullGetRewrite {
get_id : @ir.NodeId
original_local : @ir.LocalId
decision : SsaFullGetDecision
} derive(Eq, Debug)
///|
pub(all) enum SsaFullMergeInput {
ExplicitWrite(@ir.NodeId)
ParamEntry(@ir.LocalId)
DefaultEntry(@ir.LocalId)
} derive(Eq, Debug)
///|
pub(all) struct SsaFullMergeGetRewrite {
get_id : @ir.NodeId
original_local : @ir.LocalId
merge_local : @ir.LocalId
inputs : Array[SsaFullMergeInput]
} derive(Eq, Debug)
///|
pub(all) struct SsaFullRewritePlan {
write_rewrites : Array[SsaFullWriteRewrite]
get_rewrites : Array[SsaFullGetRewrite]
merge_gets : Array[SsaFullMergeGetRewrite]
} derive(Eq, Debug)
///|
fn ssa_full_write_target_local(
targets : Array[Int32],
write_id : @ir.NodeId,
) -> @ir.LocalId? {
if write_id >= 0 && write_id < targets.length() && targets[write_id] >= 0 {
Some(targets[write_id])
} else {
None
}
}
///|
fn ssa_full_source_to_merge_input(
graph : @ir.HotLocalGraph,
source : @ir.HotLocalGraphSource,
) -> SsaFullMergeInput? {
match source {
@ir.HotLocalGraphSource::SetSource(node_id) =>
Some(SsaFullMergeInput::ExplicitWrite(node_id))
@ir.HotLocalGraphSource::EntrySource(local_id) =>
if @ir.local_graph_entry_source_is_param(graph, local_id) {
Some(SsaFullMergeInput::ParamEntry(local_id))
} else if @ir.local_graph_entry_source_has_legal_default(graph, local_id) {
Some(SsaFullMergeInput::DefaultEntry(local_id))
} else {
None
}
}
}
///|
fn ssa_full_build_inputs_for_merge_get(
graph : @ir.HotLocalGraph,
get_id : @ir.NodeId,
) -> Array[SsaFullMergeInput]? {
let inputs : Array[SsaFullMergeInput] = []
for source in @ir.local_graph_reaching_sources_for_get(graph, get_id) {
match ssa_full_source_to_merge_input(graph, source) {
Some(input) => inputs.push(input)
None => return None
}
}
Some(inputs)
}
///|
fn ssa_full_plan_single_get(
graph : @ir.HotLocalGraph,
targets : Array[Int32],
get_id : @ir.NodeId,
) -> SsaFullGetDecision {
let sources = @ir.local_graph_reaching_sources_for_get(graph, get_id)
if sources.length() != 1 {
return SsaFullGetDecision::KeepCanonical
}
match sources[0] {
@ir.HotLocalGraphSource::SetSource(write_id) =>
match ssa_full_write_target_local(targets, write_id) {
Some(local_id) => SsaFullGetDecision::Retarget(local_id)
None => SsaFullGetDecision::KeepCanonical
}
@ir.HotLocalGraphSource::EntrySource(local_id) =>
if @ir.local_graph_entry_source_is_param(graph, local_id) {
SsaFullGetDecision::KeepCanonical
} else if @ir.local_graph_entry_source_has_legal_default(graph, local_id) {
SsaFullGetDecision::MaterializeDefault
} else {
SsaFullGetDecision::KeepCanonical
}
}
}
///|
pub fn ssa_full_build_merge_rewrite_plan(
func : @ir.HotFunc,
graph : @ir.HotLocalGraph,
) -> SsaFullRewritePlan {
// One plan row per node cannot rewrite distinct repeated evaluations.
if pass_has_repeated_entry_evaluation(func) {
return { write_rewrites: [], get_rewrites: [], merge_gets: [], }
}
let write_rewrites : Array[SsaFullWriteRewrite] = []
let target_by_write = Array::make(@ir.hot_node_count(func), -1)
let mut next_local = @ir.hot_local_count(func)
for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) ||
!@ir.local_graph_node_is_write(graph, node_id) {
continue
}
let original_local = @ir.local_graph_write_local_id(graph, node_id)
let decision = if @ir.local_graph_local_is_already_ssa(
graph, original_local,
) {
SsaFullWriteDecision::KeepCanonical
} else {
let fresh_local = next_local
next_local += 1
SsaFullWriteDecision::Freshen(fresh_local)
}
write_rewrites.push({ write_id: node_id, original_local, decision, })
target_by_write[node_id] = match decision {
SsaFullWriteDecision::Freshen(local_id) => local_id
SsaFullWriteDecision::KeepCanonical => original_local
}
}
let get_rewrites : Array[SsaFullGetRewrite] = []
let merge_gets : Array[SsaFullMergeGetRewrite] = []
for node_id = 0; node_id < @ir.hot_node_count(func); node_id = node_id + 1 {
if !@ir.hot_node_is_live(func, node_id) {
continue
}
let node = @ir.hot_node_get(func, node_id)
match node.op {
@ir.HotOp::LocalGet => {
let sources = @ir.local_graph_reaching_sources_for_get(graph, node_id)
if sources.length() > 1 {
match ssa_full_build_inputs_for_merge_get(graph, node_id) {
Some(inputs) => {
get_rewrites.push({
get_id: node_id,
original_local: node.imm0,
decision: SsaFullGetDecision::Merge(next_local),
})
merge_gets.push({
get_id: node_id,
original_local: node.imm0,
merge_local: next_local,
inputs,
})
next_local += 1
}
None =>
get_rewrites.push({
get_id: node_id,
original_local: node.imm0,
decision: SsaFullGetDecision::KeepCanonical,
})
}
} else {
get_rewrites.push({
get_id: node_id,
original_local: node.imm0,
decision: ssa_full_plan_single_get(graph, target_by_write, node_id),
})
}
}
_ => ()
}
}
{ write_rewrites, get_rewrites, merge_gets, }
}
///|
fn ssa_full_build_default_value(
func : @ir.HotFunc,
local_id : @ir.LocalId,
) -> @ir.NodeId? {
match @ir.hot_local_type(func, local_id) {
Some(@lib.NumTypeValType(@lib.I32NumType)) =>
Some(@ir.hot_build_const_i32(func, @lib.I32(0)))
Some(@lib.NumTypeValType(@lib.I64NumType)) =>
Some(@ir.hot_build_const_i64(func, @lib.I64(0L)))
Some(@lib.NumTypeValType(@lib.F32NumType)) =>
Some(@ir.hot_build_const_f32(func, @lib.F32(0.0)))
Some(@lib.NumTypeValType(@lib.F64NumType)) =>
Some(@ir.hot_build_const_f64(func, @lib.F64(0.0)))
Some(@lib.VecTypeValType) => Some(@ir.hot_build_const_v128_zero(func))
Some(@lib.RefTypeValType(ref_type)) if ref_type.is_nullable() =>
Some(@ir.hot_build_ref_null(func, ref_type))
_ => None
}
}
///|
fn ssa_full_validate_write_rewrite(
func : @ir.HotFunc,
rewrite : SsaFullWriteRewrite,
next_local : Int32,
) -> Int32? {
if rewrite.write_id < 0 ||
!@ir.hot_node_is_live(func, rewrite.write_id) ||
@ir.hot_child_count(func, rewrite.write_id) != 1 {
return None
}
let node = @ir.hot_node_get(func, rewrite.write_id)
match node.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => ()
_ => return None
}
if node.imm0 != rewrite.original_local ||
@ir.hot_local_type(func, rewrite.original_local) is None {
return None
}
match rewrite.decision {
SsaFullWriteDecision::Freshen(local_id) =>
if local_id == next_local {
Some(next_local + 1)
} else {
None
}
SsaFullWriteDecision::KeepCanonical => Some(next_local)
}
}
///|
fn ssa_full_validate_get_rewrite(
func : @ir.HotFunc,
rewrite : SsaFullGetRewrite,
) -> Bool {
if rewrite.get_id < 0 ||
!@ir.hot_node_is_live(func, rewrite.get_id) ||
!(@ir.hot_node_get(func, rewrite.get_id).op is @ir.HotOp::LocalGet) ||
@ir.hot_node_get(func, rewrite.get_id).imm0 != rewrite.original_local {
return false
}
match rewrite.decision {
SsaFullGetDecision::Retarget(local_id) => local_id >= 0
SsaFullGetDecision::MaterializeDefault =>
match @ir.hot_local_type(func, rewrite.original_local) {
Some(ty) => @lib.has_default(ty)
None => false
}
SsaFullGetDecision::Merge(local_id) => local_id >= 0
SsaFullGetDecision::KeepCanonical => true
}
}
///|
fn ssa_full_validate_merge_rewrite(
func : @ir.HotFunc,
rewrite : SsaFullMergeGetRewrite,
next_local : Int32,
) -> Int32? {
if rewrite.merge_local != next_local ||
rewrite.inputs.length() < 2 ||
rewrite.get_id < 0 ||
!@ir.hot_node_is_live(func, rewrite.get_id) ||
!(@ir.hot_node_get(func, rewrite.get_id).op is @ir.HotOp::LocalGet) ||
@ir.hot_node_get(func, rewrite.get_id).imm0 != rewrite.original_local ||
@ir.hot_local_type(func, rewrite.original_local) is None {
return None
}
for input in rewrite.inputs {
match input {
SsaFullMergeInput::ExplicitWrite(write_id) => {
if write_id < 0 ||
!@ir.hot_node_is_live(func, write_id) ||
@ir.hot_child_count(func, write_id) != 1 {
return None
}
let write = @ir.hot_node_get(func, write_id)
match write.op {
@ir.HotOp::LocalSet | @ir.HotOp::LocalTee => ()
_ => return None
}
}
SsaFullMergeInput::ParamEntry(local_id) =>
if local_id < 0 || local_id >= @ir.hot_param_count(func) {
return None
}
SsaFullMergeInput::DefaultEntry(local_id) =>
match @ir.hot_local_type(func, local_id) {
Some(ty) =>
if local_id < @ir.hot_param_count(func) || !@lib.has_default(ty) {
return None
}
None => return None
}
}
}
Some(next_local + 1)
}
///|
fn ssa_full_validate_plan(
func : @ir.HotFunc,
plan : SsaFullRewritePlan,
) -> Bool {
let mut next_local = @ir.hot_local_count(func)
for rewrite in plan.write_rewrites {
match ssa_full_validate_write_rewrite(func, rewrite, next_local) {
Some(next) => next_local = next
None => return false
}
}
for rewrite in plan.get_rewrites {
if !ssa_full_validate_get_rewrite(func, rewrite) {
return false
}
}
for rewrite in plan.merge_gets {
match ssa_full_validate_merge_rewrite(func, rewrite, next_local) {
Some(next) => next_local = next
None => return false
}
}
true
}
///|
fn ssa_full_replace_get_with_default(
func : @ir.HotFunc,
rewrite : SsaFullGetRewrite,
) -> Bool {
let replacement = match
ssa_full_build_default_value(func, rewrite.original_local) {
Some(node_id) => node_id
None => return false
}
@ir.hot_replace_node(
func,
rewrite.get_id,
@ir.hot_node_get(func, replacement),
)
@ir.hot_delete_node(func, replacement)
true
}
///|
fn ssa_full_apply_plan(
ctx : HotPassContext,
func : @ir.HotFunc,
plan : SsaFullRewritePlan,
) -> HotPassResult {
if !ssa_full_validate_plan(func, plan) {
return HotPassResult::unchanged()
}
let before_revision = @ir.hot_revision_current(func)
for rewrite in plan.write_rewrites {
match rewrite.decision {
SsaFullWriteDecision::Freshen(local_id) => {
let ty = @ir.hot_local_type(func, rewrite.original_local).unwrap()
let appended = @ir.hot_append_body_local(func, ty)
if appended != local_id {
abort(
"full ssa fresh-write local allocation order changed after validation",
)
}
@ir.hot_local_node_id_set(func, rewrite.write_id, local_id)
}
SsaFullWriteDecision::KeepCanonical => ()
}
}
for rewrite in plan.get_rewrites {
match rewrite.decision {
SsaFullGetDecision::Retarget(local_id) =>
@ir.hot_local_node_id_set(func, rewrite.get_id, local_id)
SsaFullGetDecision::MaterializeDefault =>
if !ssa_full_replace_get_with_default(func, rewrite) {
abort("full ssa default materialization changed after validation")
}
SsaFullGetDecision::Merge(_) | SsaFullGetDecision::KeepCanonical => ()
}
}
let prepends : Array[@ir.NodeId] = []
for rewrite in plan.merge_gets {
let merge_ty = @ir.hot_local_type(func, rewrite.original_local).unwrap()
let appended = @ir.hot_append_body_local(func, merge_ty)
if appended != rewrite.merge_local {
abort("full ssa merge-local allocation order changed after validation")
}
@ir.hot_local_node_id_set(func, rewrite.get_id, rewrite.merge_local)
for input in rewrite.inputs {
match input {
SsaFullMergeInput::ExplicitWrite(write_id) => {
let value_id = @ir.hot_child_get(func, write_id, 0)
let tee_id = @ir.hot_build_local_tee(
func,
rewrite.merge_local,
value_id,
)
@ir.hot_child_set(func, write_id, 0, tee_id)
}
SsaFullMergeInput::ParamEntry(local_id) => {
let value_id = @ir.hot_build_local_get(func, local_id)
prepends.push(
@ir.hot_build_local_set(func, rewrite.merge_local, value_id),
)
}
SsaFullMergeInput::DefaultEntry(_) => ()
}
}
}
if !prepends.is_empty() {
@ir.hot_region_insert(func, @ir.HotRegionRef::root(), 0, prepends)
}
if @ir.hot_revision_current(func) == before_revision {
HotPassResult::unchanged()
} else {
pass_mark_mutated(ctx, func)
HotPassResult::changed()
}
}
///|
priv struct SsaFullRawCursor {
mut write_cursor : Int32
mut get_cursor : Int32
mut changed : Bool
mut valid : Bool
merge_locals_by_write : Array[Array[@ir.LocalId]]
}
///|
fn ssa_full_raw_merge_map(
plan : SsaFullRewritePlan,
) -> Array[Array[@ir.LocalId]] {
let mut max_write_id = -1
for rewrite in plan.write_rewrites {
if rewrite.write_id > max_write_id {
max_write_id = rewrite.write_id
}
}
let out : Array[Array[@ir.LocalId]] = []
for _ in 0..<(max_write_id + 1) {
out.push([])
}
for merge in plan.merge_gets {
for input in merge.inputs {
match input {
SsaFullMergeInput::ExplicitWrite(write_id) =>
if write_id >= 0 && write_id < out.length() {
out[write_id].push(merge.merge_local)
}
_ => ()
}
}
}
out
}
///|
fn ssa_full_raw_rewrite_body(
instrs : Array[@lib.Instruction],
params : Array[@lib.ValType],
locals : @lib.Locals,
plan : SsaFullRewritePlan,
cursor : SsaFullRawCursor,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
if cursor.get_cursor >= plan.get_rewrites.length() {
cursor.valid = false
out.push(instr)
continue
}
let rewrite = plan.get_rewrites[cursor.get_cursor]
cursor.get_cursor += 1
let original_local = raw.reinterpret_as_int()
if rewrite.original_local != original_local {
cursor.valid = false
out.push(instr)
continue
}
match rewrite.decision {
SsaFullGetDecision::Retarget(local_id)
| SsaFullGetDecision::Merge(local_id) => {
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(local_id.reinterpret_as_uint()),
),
)
if local_id != original_local {
cursor.changed = true
}
}
SsaFullGetDecision::MaterializeDefault => {
out.push(
run_hot_pipeline_raw_default_instr_for_local(
params, locals, original_local,
),
)
cursor.changed = true
}
SsaFullGetDecision::KeepCanonical => out.push(instr)
}
}
@lib.LocalSet(@lib.LocalIdx(raw)) | @lib.LocalTee(@lib.LocalIdx(raw)) => {
if cursor.write_cursor >= plan.write_rewrites.length() {
cursor.valid = false
out.push(instr)
continue
}
let rewrite = plan.write_rewrites[cursor.write_cursor]
cursor.write_cursor += 1
let original_local = raw.reinterpret_as_int()
if rewrite.original_local != original_local {
cursor.valid = false
out.push(instr)
continue
}
let merge_locals = if rewrite.write_id >= 0 &&
rewrite.write_id < cursor.merge_locals_by_write.length() {
cursor.merge_locals_by_write[rewrite.write_id]
} else {
[]
}
for merge_local in merge_locals {
out.push(
@lib.Instruction::local_tee(
@lib.LocalIdx::new(merge_local.reinterpret_as_uint()),
),
)
cursor.changed = true
}
let target_local = match rewrite.decision {
SsaFullWriteDecision::Freshen(local_id) => {
cursor.changed = true
local_id
}
SsaFullWriteDecision::KeepCanonical => original_local
}
let target = @lib.LocalIdx::new(target_local.reinterpret_as_uint())
match instr {
@lib.LocalSet(_) => out.push(@lib.Instruction::local_set(target))
@lib.LocalTee(_) => out.push(@lib.Instruction::local_tee(target))
_ =>
abort("full ssa raw local write dispatch lost its instruction kind")
}
}
@lib.Block(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
ssa_full_raw_rewrite_body(body, params, locals, plan, cursor),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
ssa_full_raw_rewrite_body(body, params, locals, plan, cursor),
),
),
)
@lib.If(block_type, then_body, else_body) => {
let rewritten_then = ssa_full_raw_rewrite_body(
then_body, params, locals, plan, cursor,
)
let rewritten_else = match else_body {
Some(body) =>
Some(ssa_full_raw_rewrite_body(body, params, locals, plan, cursor))
None => None
}
out.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
ssa_full_raw_rewrite_body(body, params, locals, plan, cursor),
),
),
)
_ => out.push(instr)
}
}
out
}
///|
fn ssa_full_raw_prepends(plan : SsaFullRewritePlan) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for merge in plan.merge_gets {
for input in merge.inputs {
match input {
SsaFullMergeInput::ParamEntry(local_id) => {
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(local_id.reinterpret_as_uint()),
),
)
out.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(merge.merge_local.reinterpret_as_uint()),
),
)
}
_ => ()
}
}
}
out
}
///|
fn ssa_full_rewrite_raw_func(
original : @lib.Func,
params : Array[@lib.ValType],
plan : SsaFullRewritePlan,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(instrs)) = original
let cursor = {
write_cursor: 0,
get_cursor: 0,
changed: false,
valid: true,
merge_locals_by_write: ssa_full_raw_merge_map(plan),
}
let rewritten_body = ssa_full_raw_rewrite_body(
instrs, params, locals, plan, cursor,
)
if !cursor.valid ||
cursor.write_cursor != plan.write_rewrites.length() ||
cursor.get_cursor != plan.get_rewrites.length() {
return (original, false)
}
let extra_local_types : Array[@lib.ValType] = []
let mut next_local = params.length() + locals.length()
for rewrite in plan.write_rewrites {
match rewrite.decision {
SsaFullWriteDecision::Freshen(local_id) => {
if local_id != next_local {
return (original, false)
}
extra_local_types.push(
run_hot_pipeline_raw_local_type(
params,
locals,
rewrite.original_local,
),
)
next_local += 1
}
SsaFullWriteDecision::KeepCanonical => ()
}
}
for merge in plan.merge_gets {
if merge.merge_local != next_local {
return (original, false)
}
extra_local_types.push(
run_hot_pipeline_raw_local_type(params, locals, merge.original_local),
)
next_local += 1
}
let prepends = ssa_full_raw_prepends(plan)
let final_body = prepends
final_body.append(rewritten_body)
let rewritten_locals = locals.copy()
rewritten_locals.append_types(extra_local_types)
let changed = cursor.changed ||
!prepends.is_empty() ||
!extra_local_types.is_empty()
if changed {
(@lib.Func::new(rewritten_locals, @lib.Expr::new(final_body)), true)
} else {
(original, false)
}
}
///|
fn ssa_full_run(ctx : HotPassContext, func : @ir.HotFunc) -> HotPassResult {
if pass_has_repeated_entry_evaluation(func) {
return HotPassResult::unchanged()
}
let cfg = @ir.cfg_build(func, expand_operand_control=true)
let graph = @ir.local_graph_build_full_flow(func, cfg)
let plan = ssa_full_build_merge_rewrite_plan(func, graph)
ssa_full_apply_plan(ctx, func, plan)
}