commit 2e4a28a4b42fbbc07eb68dd16b689b5bd1b77fcf
parent 971f78285dc58ebca6711d7cd4f04d8b56c7a5af
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 28 May 2026 20:33:43 -0700
Prune unreachable static AArch64 O1 functions
Diffstat:
9 files changed, 387 insertions(+), 1 deletion(-)
diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c
@@ -221,6 +221,11 @@ void debug_func_begin(Debug* d, ObjSymId sym, DebugTypeId fn_type,
(void)decl;
}
+void debug_func_select(Debug* d, ObjSymId sym) {
+ (void)d;
+ (void)sym;
+}
+
void debug_func_pc_range(Debug* d, ObjSecId text_section_id, u32 begin_ofs,
u32 end_ofs) {
(void)d;
@@ -230,6 +235,9 @@ void debug_func_pc_range(Debug* d, ObjSecId text_section_id, u32 begin_ofs,
}
void debug_func_end(Debug* d) { (void)d; }
+
+void debug_prune_removed_funcs(Debug* d) { (void)d; }
+
void debug_scope_begin(Debug* d, SrcLoc loc) {
(void)d;
(void)loc;
diff --git a/src/cg/ir.c b/src/cg/ir.c
@@ -69,6 +69,36 @@ CgIrModule* cg_ir_module_new(Compiler* c) {
return m;
}
+void cg_ir_symset_init_lazy(Compiler* c, ObjSymSet* s) {
+ if (!s || s->heap) return;
+ ObjSymSet_init_cap(s, c ? c->ctx->heap : NULL, 0);
+}
+
+void cg_ir_symset_add(Compiler* c, ObjSymSet* s, ObjSymId sym) {
+ if (!s || sym == OBJ_SYM_NONE) return;
+ cg_ir_symset_init_lazy(c, s);
+ if (!s->heap) return;
+ (void)ObjSymSet_set(s, sym, 1);
+}
+
+int cg_ir_symset_contains(const ObjSymSet* s, ObjSymId sym) {
+ return s && ObjSymSet_get(s, sym) != NULL;
+}
+
+void cg_ir_symset_fini(ObjSymSet* s) {
+ if (s && s->heap) ObjSymSet_fini(s);
+}
+
+void cg_ir_module_refsets_fini(CgIrModule* m) {
+ if (!m) return;
+ for (u32 i = 0; i < m->nfuncs; ++i) {
+ CgIrFunc* f = m->funcs[i];
+ if (!f) continue;
+ cg_ir_symset_fini(&f->call_refs);
+ cg_ir_symset_fini(&f->global_refs);
+ }
+}
+
void cg_ir_module_add_func(CgIrModule* m, CgIrFunc* f) {
if (!m || !f) return;
module_grow(m, m->nfuncs + 1u);
diff --git a/src/cg/ir.h b/src/cg/ir.h
@@ -3,6 +3,9 @@
#include "cg/cgtarget.h"
#include "core/arena.h"
+#include "core/hashmap.h"
+
+HASHMAP_DEFINE(ObjSymSet, ObjSymId, u8, hash_u32);
typedef enum CgIrOp {
CG_IR_NOP,
@@ -216,6 +219,9 @@ typedef struct CgIrFunc {
u32 nscopes;
u32 scopes_cap;
+ ObjSymSet call_refs;
+ ObjSymSet global_refs;
+
u32 next_inst_id;
u8 complete;
u8 pad[3];
@@ -256,6 +262,12 @@ void cg_ir_module_add_func(CgIrModule*, CgIrFunc*);
void cg_ir_module_add_alias(CgIrModule*, ObjSymId alias_sym,
ObjSymId target_sym, CfreeCgTypeId type);
void cg_ir_module_add_file_scope_asm(CgIrModule*, const char* src, size_t len);
+void cg_ir_module_refsets_fini(CgIrModule*);
+
+void cg_ir_symset_init_lazy(Compiler*, ObjSymSet*);
+void cg_ir_symset_add(Compiler*, ObjSymSet*, ObjSymId);
+int cg_ir_symset_contains(const ObjSymSet*, ObjSymId);
+void cg_ir_symset_fini(ObjSymSet*);
CGLocal cg_ir_func_add_local(CgIrFunc*, const CGLocalDesc*, int is_param,
u32 param_index);
diff --git a/src/cg/ir_recorder.c b/src/cg/ir_recorder.c
@@ -43,9 +43,21 @@ static CgIrInst* emit(CgIrRecorder* r, CgIrOp op) {
return cg_ir_emit(require_func(r), op, r->loc);
}
+static void note_global_ref(CgIrRecorder* r, Operand op) {
+ if (op.kind == OPK_GLOBAL)
+ cg_ir_symset_add(r->base.c, &require_func(r)->global_refs,
+ op.v.global.sym);
+}
+
+static void note_global_refs(CgIrRecorder* r, const Operand* ops, u32 n) {
+ if (!ops) return;
+ for (u32 i = 0; i < n; ++i) note_global_ref(r, ops[i]);
+}
+
static void set_ops(CgIrRecorder* r, CgIrInst* in, const Operand* ops, u32 n) {
in->opnds = cg_ir_dup_operands(require_func(r)->arena, ops, n);
in->nopnds = n;
+ note_global_refs(r, ops, n);
}
static void rec_func_begin(CgTarget* t, const CGFuncDesc* desc) {
@@ -312,6 +324,7 @@ static void rec_tls_addr_of(CgTarget* t, Operand dst, ObjSymId sym,
aux->sym = sym;
aux->addend = addend;
set_ops(r, in, &dst, 1);
+ cg_ir_symset_add(r->base.c, &require_func(r)->global_refs, sym);
in->extra.aux = aux;
}
@@ -393,6 +406,11 @@ static void rec_call(CgTarget* t, const CGCallDesc* desc) {
CgIrInst* in = emit(r, CG_IR_CALL);
CgIrCallAux* aux = AUX_NEW(r, CgIrCallAux);
aux->desc = cg_ir_dup_call_desc(require_func(r)->arena, desc);
+ note_global_ref(r, desc->callee);
+ if (desc->callee.kind == OPK_GLOBAL && desc->callee.v.global.addend == 0) {
+ cg_ir_symset_add(r->base.c, &require_func(r)->call_refs,
+ desc->callee.v.global.sym);
+ }
in->extra.aux = aux;
}
@@ -512,6 +530,8 @@ static void rec_intrinsic(CgTarget* t, IntrinKind kind, Operand* dsts, u32 ndst,
aux->args = cg_ir_dup_operands(require_func(r)->arena, args, narg);
aux->ndst = ndst;
aux->narg = narg;
+ note_global_refs(r, dsts, ndst);
+ note_global_refs(r, args, narg);
in->extra.aux = aux;
}
@@ -529,6 +549,8 @@ static void rec_asm_block(CgTarget* t, const char* tmpl,
aux->out_ops = cg_ir_dup_operands(f->arena, out_ops, nout);
aux->ins = cg_ir_dup_asm_constraints(f->arena, ins, nin);
aux->in_ops = cg_ir_dup_operands(f->arena, in_ops, nin);
+ note_global_refs(r, out_ops, nout);
+ note_global_refs(r, in_ops, nin);
if (nclob) {
aux->clobbers = arena_array(f->arena, Sym, nclob);
memcpy(aux->clobbers, clobbers, sizeof(*aux->clobbers) * nclob);
@@ -555,6 +577,7 @@ static void rec_finalize(CgTarget* t) {
static void rec_destroy(CgTarget* t) {
CgIrRecorder* r = rec_of(t);
if (r->destroy_user) r->destroy_user(r->user);
+ cg_ir_module_refsets_fini(r->module);
}
CgTarget* cg_ir_recorder_new(Compiler* c, ObjBuilder* obj,
diff --git a/src/debug/debug.c b/src/debug/debug.c
@@ -357,6 +357,16 @@ void debug_func_begin(Debug* d, ObjSymId sym, DebugTypeId fn_type,
d->nfuncs++;
}
+void debug_func_select(Debug* d, ObjSymId sym) {
+ if (!d || sym == OBJ_SYM_NONE) return;
+ for (u32 i = 0; i < d->nfuncs; ++i) {
+ if (d->funcs[i].sym == sym) {
+ d->cur_func = (i32)i;
+ return;
+ }
+ }
+}
+
void debug_func_pc_range(Debug* d, ObjSecId text_section, u32 begin_ofs,
u32 end_ofs) {
if (d->cur_func < 0) return;
@@ -374,6 +384,23 @@ void debug_func_end(Debug* d) {
d->cur_func = -1;
}
+void debug_prune_removed_funcs(Debug* d) {
+ u32 w = 0;
+ if (!d) return;
+ for (u32 r = 0; r < d->nfuncs; ++r) {
+ DebugFunc* f = &d->funcs[r];
+ const ObjSym* sym = obj_symbol_get(d->ob, f->sym);
+ if (!sym || sym->removed) {
+ func_free(d, f);
+ continue;
+ }
+ if (w != r) d->funcs[w] = *f;
+ ++w;
+ }
+ d->nfuncs = w;
+ d->cur_func = -1;
+}
+
/* ---- scopes ---- */
void debug_scope_begin(Debug* d, SrcLoc loc) {
diff --git a/src/debug/debug.h b/src/debug/debug.h
@@ -93,9 +93,11 @@ DebugTypeId debug_type_enum_end(DebugEnumBuilder*);
* ============================================================ */
void debug_func_begin(Debug*, ObjSymId, DebugTypeId fn_type, SrcLoc decl);
+void debug_func_select(Debug*, ObjSymId);
void debug_func_pc_range(Debug*, ObjSecId text_section_id, u32 begin_ofs,
u32 end_ofs);
void debug_func_end(Debug*);
+void debug_prune_removed_funcs(Debug*);
/* lexical scopes (nested between func_begin/end) */
void debug_scope_begin(Debug*, SrcLoc);
diff --git a/src/opt/opt.c b/src/opt/opt.c
@@ -7,8 +7,10 @@
#include "cg/type.h"
#include "core/arena.h"
#include "core/core.h"
+#include "core/hashmap.h"
#include "core/metrics.h"
#include "core/strbuf.h"
+#include "debug/debug.h"
#include "opt/opt_internal.h"
#undef Operand
@@ -33,6 +35,8 @@ typedef struct OptImpl {
u32 cg_cap;
} OptImpl;
+HASHMAP_DEFINE(OptFuncIndex, ObjSymId, u32, hash_u32);
+
/* Lazily re-lower (and cache) the pre-machinize Func for a recorded callee
* symbol. Returns NULL for forward-defined callees not yet recorded. */
static Func* opt_tiny_callee_lookup(void* ctx, ObjSymId sym) {
@@ -210,7 +214,11 @@ static void opt_run_o1_native(OptImpl* o, Func* f) {
opt_dbg_dump(o, f, "pre-emit");
metrics_scope_begin(o->c, "opt.emit");
+ if (o->native->mc && o->native->mc->debug)
+ debug_func_select(o->native->mc->debug, f->desc.sym);
opt_emit_native(o->c, f, o->native);
+ if (o->native->mc && o->native->mc->debug)
+ debug_func_end(o->native->mc->debug);
metrics_scope_end(o->c, "opt.emit");
metrics_scope_end(o->c, "opt.o1.total");
}
@@ -238,12 +246,180 @@ static void opt_on_func(void* user, CgIrFunc* cg_func) {
/* The dump writer renders the semantic CG IR tape — the IR as recorded,
* before lowering to the optimizer's CFG form. */
if (o->dump_writer) cg_ir_func_dump(cg_func, o->dump_writer);
+ if (o->c->target.arch == CFREE_ARCH_ARM_64) return;
metrics_scope_begin(o->c, "opt.o1.cg_ir_lower");
f = opt_func_from_cg_ir(o->c, cg_func);
metrics_scope_end(o->c, "opt.o1.cg_ir_lower");
opt_run_o1_native(o, f);
}
+static int opt_func_is_root(OptImpl* o, const CgIrFunc* f) {
+ const ObjSym* s;
+ if (!f || f->desc.sym == OBJ_SYM_NONE) return 0;
+ s = obj_symbol_get(o->target->obj, f->desc.sym);
+ if (!s || s->removed) return 0;
+ if (s->bind != SB_LOCAL) return 1;
+ if (s->flags & CFREE_CG_SYM_USED) return 1;
+ return 0;
+}
+
+static void opt_mark_func(u8* reachable, u8* queued, u32* queue, u32* qtail,
+ u32 idx) {
+ if (reachable[idx]) return;
+ reachable[idx] = 1;
+ if (!queued[idx]) {
+ queued[idx] = 1;
+ queue[(*qtail)++] = idx;
+ }
+}
+
+static void opt_mark_sym(OptFuncIndex* index, u8* reachable, u8* queued,
+ u32* queue, u32* qtail, ObjSymId sym) {
+ u32* slot;
+ if (sym == OBJ_SYM_NONE) return;
+ slot = OptFuncIndex_get(index, sym);
+ if (slot) opt_mark_func(reachable, queued, queue, qtail, *slot);
+}
+
+static void opt_mark_symset(OptFuncIndex* index, u8* reachable, u8* queued,
+ u32* queue, u32* qtail,
+ const ObjSymSet* refs) {
+ if (!refs || !refs->cap) return;
+ for (u32 i = 0; i < refs->cap; ++i) {
+ ObjSymId sym = refs->slots[i].k;
+ if (sym != OBJ_SYM_NONE)
+ opt_mark_sym(index, reachable, queued, queue, qtail, sym);
+ }
+}
+
+static int opt_data_reloc_is_exported_root(OptImpl* o, const Reloc* r) {
+ ObjSymIter* it;
+ ObjSymEntry ent;
+ const Section* sec;
+ if (!r || r->removed || r->section_id == OBJ_SEC_NONE) return 0;
+ sec = obj_section_get(o->target->obj, r->section_id);
+ if (!sec || sec->removed || sec->kind == SEC_TEXT) return 0;
+ if (sec->flags & SF_RETAIN) return 1;
+ it = obj_symiter_new(o->target->obj);
+ if (!it) return 0;
+ while (obj_symiter_next(it, &ent)) {
+ const ObjSym* s = ent.sym;
+ u64 begin, end;
+ if (!s || s->removed || s->section_id != r->section_id) continue;
+ if (s->kind != SK_OBJ && s->kind != SK_TLS && s->kind != SK_COMMON)
+ continue;
+ if (s->bind == SB_LOCAL && !(s->flags & CFREE_CG_SYM_USED)) continue;
+ begin = s->value;
+ end = begin + s->size;
+ if (s->size == 0) end = begin + 1u;
+ if ((u64)r->offset >= begin && (u64)r->offset < end) {
+ obj_symiter_free(it);
+ return 1;
+ }
+ }
+ obj_symiter_free(it);
+ return 0;
+}
+
+static void opt_root_exported_data_relocs(OptImpl* o, OptFuncIndex* index,
+ u8* reachable, u8* queued,
+ u32* queue, u32* qtail) {
+ u32 nrel = obj_reloc_total(o->target->obj);
+ for (u32 i = 0; i < nrel; ++i) {
+ const Reloc* r = obj_reloc_at(o->target->obj, i);
+ if (opt_data_reloc_is_exported_root(o, r))
+ opt_mark_sym(index, reachable, queued, queue, qtail, r->sym);
+ }
+}
+
+static void opt_root_aliases(OptImpl* o, const CgIrModule* module,
+ OptFuncIndex* index, u8* reachable, u8* queued,
+ u32* queue, u32* qtail) {
+ for (u32 i = 0; module && i < module->naliases; ++i) {
+ const CgIrAlias* a = &module->aliases[i];
+ const ObjSym* s = obj_symbol_get(o->target->obj, a->alias_sym);
+ if (!s || s->removed) continue;
+ if (s->bind != SB_LOCAL || (s->flags & CFREE_CG_SYM_USED))
+ opt_mark_sym(index, reachable, queued, queue, qtail, a->target_sym);
+ }
+}
+
+static void opt_refresh_or_prune_aliases(OptImpl* o, const CgIrModule* module,
+ OptFuncIndex* index,
+ const u8* reachable) {
+ for (u32 i = 0; module && i < module->naliases; ++i) {
+ const CgIrAlias* a = &module->aliases[i];
+ const ObjSym* ts;
+ const ObjSym* as;
+ u32* target_idx = OptFuncIndex_get(index, a->target_sym);
+ if (!target_idx || !reachable[*target_idx]) {
+ as = obj_symbol_get(o->target->obj, a->alias_sym);
+ if (as && as->bind == SB_LOCAL)
+ obj_symbol_remove(o->target->obj, a->alias_sym);
+ continue;
+ }
+ ts = obj_symbol_get(o->target->obj, a->target_sym);
+ if (ts && !ts->removed && ts->section_id != OBJ_SEC_NONE)
+ obj_symbol_define(o->target->obj, a->alias_sym, ts->section_id,
+ ts->value, ts->size);
+ }
+}
+
+static void opt_prune_debug(OptImpl* o) {
+ if (o->native && o->native->mc && o->native->mc->debug)
+ debug_prune_removed_funcs(o->native->mc->debug);
+}
+
+static void opt_emit_reachable_aarch64(OptImpl* o, const CgIrModule* module) {
+ OptFuncIndex index;
+ u8* reachable;
+ u8* queued;
+ u32* queue;
+ u32 qhead = 0;
+ u32 qtail = 0;
+ if (!module || !module->nfuncs) return;
+ OptFuncIndex_init_cap(&index, o->c->ctx->heap, 0);
+ reachable = arena_zarray(o->c->tu, u8, module->nfuncs);
+ queued = arena_zarray(o->c->tu, u8, module->nfuncs);
+ queue = arena_array(o->c->tu, u32, module->nfuncs);
+ for (u32 i = 0; i < module->nfuncs; ++i) {
+ CgIrFunc* f = module->funcs[i];
+ if (f && f->desc.sym != OBJ_SYM_NONE)
+ (void)OptFuncIndex_set(&index, f->desc.sym, i);
+ }
+ for (u32 i = 0; i < module->nfuncs; ++i) {
+ if (module->nfile_scope_asms || opt_func_is_root(o, module->funcs[i]))
+ opt_mark_func(reachable, queued, queue, &qtail, i);
+ }
+ opt_root_aliases(o, module, &index, reachable, queued, queue, &qtail);
+ opt_root_exported_data_relocs(o, &index, reachable, queued, queue, &qtail);
+ while (qhead < qtail) {
+ CgIrFunc* f = module->funcs[queue[qhead++]];
+ opt_mark_symset(&index, reachable, queued, queue, &qtail, &f->call_refs);
+ opt_mark_symset(&index, reachable, queued, queue, &qtail, &f->global_refs);
+ }
+ for (u32 i = 0; i < module->nfuncs; ++i) {
+ CgIrFunc* cg_func = module->funcs[i];
+ if (reachable[i]) continue;
+ if (cg_func && cg_func->desc.sym != OBJ_SYM_NONE) {
+ const ObjSym* s = obj_symbol_get(o->target->obj, cg_func->desc.sym);
+ if (s && s->bind == SB_LOCAL)
+ obj_symbol_remove(o->target->obj, cg_func->desc.sym);
+ }
+ }
+ opt_prune_debug(o);
+ for (u32 i = 0; i < module->nfuncs; ++i) {
+ Func* f;
+ if (!reachable[i]) continue;
+ metrics_scope_begin(o->c, "opt.o1.cg_ir_lower");
+ f = opt_func_from_cg_ir(o->c, module->funcs[i]);
+ metrics_scope_end(o->c, "opt.o1.cg_ir_lower");
+ opt_run_o1_native(o, f);
+ }
+ opt_refresh_or_prune_aliases(o, module, &index, reachable);
+ OptFuncIndex_fini(&index);
+}
+
static void opt_on_finalize(void* user, const CgIrModule* module) {
OptImpl* o = (OptImpl*)user;
/* File-scope asm blocks are captured during recording (no live target then)
@@ -254,6 +430,8 @@ static void opt_on_finalize(void* user, const CgIrModule* module) {
o->native->file_scope_asm(o->native, module->file_scope_asms[i].src,
module->file_scope_asms[i].len);
}
+ if (o->c->target.arch == CFREE_ARCH_ARM_64)
+ opt_emit_reachable_aarch64(o, module);
if (o->native && o->native->finalize) o->native->finalize(o->native);
}
diff --git a/test/opt/static_prune_aa64.sh b/test/opt/static_prune_aa64.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# Structural checks for AArch64 O1 static-function pruning.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+CFREE="${CFREE:-$ROOT/build/cfree}"
+WORK="$ROOT/build/test/opt/static_prune_aa64"
+mkdir -p "$WORK"
+
+compile_case() {
+ local name=$1
+ local src="$WORK/$name.c"
+ local obj="$WORK/$name.o"
+ cat > "$src"
+ "$CFREE" cc -target aarch64-linux-gnu -O1 -std=c11 -c "$src" \
+ -o "$obj" > "$WORK/$name.cc.out" 2>&1
+ "$CFREE" nm "$obj" > "$WORK/$name.nm" 2>&1
+}
+
+has_sym() {
+ local name=$1
+ local sym=$2
+ grep -Eq "[[:space:]]$sym$" "$WORK/$name.nm"
+}
+
+want_sym() {
+ local name=$1
+ local sym=$2
+ if ! has_sym "$name" "$sym"; then
+ printf 'static-prune FAILED: %s missing expected symbol %s\n' \
+ "$name" "$sym" >&2
+ sed 's/^/ | /' "$WORK/$name.nm" >&2
+ exit 1
+ fi
+}
+
+want_no_sym() {
+ local name=$1
+ local sym=$2
+ if has_sym "$name" "$sym"; then
+ printf 'static-prune FAILED: %s kept unexpected symbol %s\n' \
+ "$name" "$sym" >&2
+ sed 's/^/ | /' "$WORK/$name.nm" >&2
+ exit 1
+ fi
+}
+
+compile_case unused_static <<'EOF'
+static int dead_unused(void) { return 1; }
+int exported_live(void) { return 2; }
+EOF
+want_no_sym unused_static dead_unused
+want_sym unused_static exported_live
+
+compile_case reached_static <<'EOF'
+static int reached_helper(void) { return 3; }
+int exported_calls_helper(void) { return reached_helper(); }
+EOF
+want_sym reached_static reached_helper
+want_sym reached_static exported_calls_helper
+
+compile_case call_chain <<'EOF'
+static int chain_leaf(void) { return 4; }
+static int chain_mid(void) { return chain_leaf(); }
+int exported_chain(void) { return chain_mid(); }
+EOF
+want_sym call_chain chain_leaf
+want_sym call_chain chain_mid
+
+compile_case global_fnptr <<'EOF'
+static int pointed_target(void) { return 5; }
+int (*exported_fp)(void) = pointed_target;
+EOF
+want_sym global_fnptr pointed_target
+want_sym global_fnptr exported_fp
+
+compile_case used_attr <<'EOF'
+static int used_target(void) __attribute__((used));
+static int used_target(void) { return 6; }
+int exported_for_used_case(void) { return 0; }
+EOF
+want_sym used_attr used_target
+
+compile_case alias_root <<'EOF'
+static int aliased_target(void) { return 7; }
+int exported_alias(void) __attribute__((alias("aliased_target")));
+EOF
+want_sym alias_root aliased_target
+want_sym alias_root exported_alias
+
+compile_case file_scope_asm <<'EOF'
+__asm__(".text\n");
+static int asm_conserved_local(void) { return 8; }
+int exported_with_asm(void) { return 0; }
+EOF
+want_sym file_scope_asm asm_conserved_local
+
+printf 'static-prune-aa64: ok\n'
diff --git a/test/test.mk b/test/test.mk
@@ -223,6 +223,10 @@ $(DEBUG_TEST_BIN): test/debug/roundtrip_unit.c $(LIB_OBJS)
test-dbg: bin
@CFREE=$(abspath $(BIN)) sh test/dbg/run.sh
+.PHONY: test-dbg-red
+test-dbg-red: bin
+ @CFREE=$(abspath $(BIN)) DBG_STRICT_XFAIL=1 sh test/dbg/run.sh
+
# aa64 ISA descriptor-table unit test (doc/ASM.md phase 2). Covers
# every AA64Format the table maps and the alias-precedence invariant
# (first-match disasm picks the alias spelling over the canonical
@@ -517,7 +521,7 @@ test-macho: lib $(TEST_RT_DEP) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_R
OPT_TEST_BIN = build/test/cg_ir_lower_test
TINY_INLINE_TEST_BIN = build/test/tiny_inline_test
-test-opt: bin $(OPT_TEST_BIN) test-opt-tiny-inline test-opt-inline test-opt-zero-arg
+test-opt: bin $(OPT_TEST_BIN) test-opt-tiny-inline test-opt-inline test-opt-zero-arg test-opt-static-prune-aa64
$(OPT_TEST_BIN)
$(OPT_TEST_BIN): test/opt/cg_ir_lower_test.c $(LIB_OBJS)
@@ -540,6 +544,10 @@ test-opt-inline: bin
test-opt-zero-arg: bin
@CFREE=$(abspath $(BIN)) bash test/opt/zero_arg.sh
+.PHONY: test-opt-static-prune-aa64
+test-opt-static-prune-aa64: bin
+ @CFREE=$(abspath $(BIN)) bash test/opt/static_prune_aa64.sh
+
test-parse: test-parse-ok test-parse-err
test-parse-ok: lib $(TEST_RT_DEP) $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)