commit 5194cb9d9957c73acaf9b0aec66baee5efa53e7c
parent 2eb08459e47cccd6938a04bb08f2f09cc68e7b09
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 20 May 2026 19:58:22 -0700
test: broaden switch corpus and seed jump-table red shape test
Lays the test groundwork before the O0/O1 switch-to-jump-table
lowering. Behavior is unchanged today: every new case passes under
the chain lowering and must keep passing once a table emitter lands.
Frontend (toy):
125_switch_dense_boundaries — dense 10..20 with default, exercises
vmin/vmax/below/above/interior-gap selectors a table lowering
must bounds-check correctly.
126_switch_sparse_chain — sparse {1,50,1000}, pins chain fallback
so a tuning regression that relaxes density thresholds is caught.
127_switch_forced_jump_table — forced .jump_table over a dense
16-case no-default block; synthesized default arm must be reachable.
Frontend (C):
6_8_29_switch_signed_negative — signed dense -3..3, exercises
(sel - vmin) compute when vmin is negative.
6_8_30_switch_sparse_chain — C-side equivalent of the toy sparse case.
API:
test/api/cg_switch_test.c — drives cfree_cg_switch directly with
hand-built descriptors across all hint variants, density shapes,
signed/unsigned, default/no-default, single, and empty (default-only).
Wired into test-cg-api alongside cg_type_test / abi_classify_test.
Red shape test:
127_switch_forced_jump_table.objdump — asserts that the forced-hint
case produces a __TEXT,__const section and a .Lcfree_jt symbol. Fails
today (no table emitted yet); turns green when the emitter lands.
Diffstat:
13 files changed, 478 insertions(+), 1 deletion(-)
diff --git a/test/api/cg_switch_test.c b/test/api/cg_switch_test.c
@@ -0,0 +1,315 @@
+/* cg_switch_test — drives cfree_cg_switch with hand-built descriptors
+ * covering the shapes the lowering must handle. Structural: asserts that
+ * each shape lowers without panic and produces a defined function
+ * symbol. Behavior under different lowering strategies (chain vs table)
+ * is covered end-to-end by test/toy and test/parse — this file targets
+ * the API surface itself.
+ *
+ * Shapes covered:
+ * - hint = TARGET_DEFAULT / BRANCH_CHAIN / JUMP_TABLE
+ * - dense range, sparse range, signed dense range straddling zero
+ * - default present / default absent
+ * - single case, empty cases (default-only)
+ * - both opt levels (0 and 1)
+ *
+ * Run by: make test-cg-api
+ */
+
+#include <cfree/cg.h>
+#include <cfree/core.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "core/core.h"
+#include "obj/obj.h"
+
+static void* h_alloc(CfreeHeap* h, size_t n, size_t a) {
+ (void)h;
+ (void)a;
+ return n ? malloc(n) : NULL;
+}
+static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) {
+ (void)h;
+ (void)o;
+ (void)a;
+ return realloc(p, n);
+}
+static void h_free(CfreeHeap* h, void* p, size_t n) {
+ (void)h;
+ (void)n;
+ free(p);
+}
+static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
+
+static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
+ const char* fmt, va_list ap) {
+ static const char* names[] = {"note", "warning", "error", "fatal"};
+ (void)s;
+ (void)loc;
+ fprintf(stderr, "%s: ", names[k]);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+}
+static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0};
+
+static int g_fail;
+
+#define EXPECT(cond, ...) \
+ do { \
+ if (!(cond)) { \
+ ++g_fail; \
+ fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fputc('\n', stderr); \
+ } \
+ } while (0)
+
+/* ---- Helpers --------------------------------------------------------- */
+
+typedef struct SwitchShape {
+ const char* name;
+ CfreeCgTypeId selector_type;
+ /* values[] interpreted by selector_type; arm i is reached when the
+ * selector equals values[i]. Each arm returns (uint32_t)results[i]. */
+ const int64_t* values;
+ const int32_t* results;
+ uint32_t ncases;
+ int has_default;
+ int32_t default_result; /* used if has_default; otherwise arm falls past */
+ CfreeCgSwitchHint hint;
+} SwitchShape;
+
+/* Build:
+ * int32_t f(<selector_type> x) {
+ * switch (x) {
+ * case values[0]: return results[0];
+ * ...
+ * default: return default_result; // if has_default
+ * }
+ * return -1; // fall-through past switch
+ * }
+ */
+static void build_switch_fn(CfreeCompiler* c, CfreeCgTypeId i32_ty,
+ const SwitchShape* sh, int opt_level) {
+ char fn_name[96];
+ CfreeCodeOptions opts;
+ CfreeObjBuilder* ob;
+ CfreeCg* cg;
+ CfreeCgFuncParam param_desc;
+ CfreeCgFuncSig sig;
+ CfreeCgDecl decl;
+ CfreeCgSym sym;
+ CfreeCgLocalAttrs attrs;
+ CfreeCgLocal param;
+ CfreeCgLabel default_lbl;
+ CfreeCgLabel end_lbl;
+ CfreeCgLabel* case_lbls = NULL;
+ CfreeCgSwitchCase* cases = NULL;
+ CfreeCgSwitch sw;
+ uint32_t i;
+
+ memset(&opts, 0, sizeof opts);
+ opts.opt_level = opt_level;
+ ob = (CfreeObjBuilder*)obj_new((Compiler*)c);
+ EXPECT(ob != NULL, "[%s/O%d] obj_new failed", sh->name, opt_level);
+ if (!ob) return;
+ cg = NULL;
+ (void)cfree_cg_new(c, ob, &opts, &cg);
+ EXPECT(cg != NULL, "[%s/O%d] cg_new failed", sh->name, opt_level);
+ if (!cg) {
+ obj_free((ObjBuilder*)ob);
+ return;
+ }
+
+ memset(¶m_desc, 0, sizeof param_desc);
+ param_desc.type = sh->selector_type;
+ memset(&sig, 0, sizeof sig);
+ sig.ret = i32_ty;
+ sig.params = ¶m_desc;
+ sig.nparams = 1;
+ sig.call_conv = CFREE_CG_CC_TARGET_C;
+
+ snprintf(fn_name, sizeof fn_name, "switch_%s_o%d", sh->name, opt_level);
+ memset(&decl, 0, sizeof decl);
+ decl.kind = CFREE_CG_DECL_FUNC;
+ decl.linkage_name = cfree_sym_intern(c, fn_name);
+ decl.display_name = decl.linkage_name;
+ decl.type = cfree_cg_type_func(c, sig);
+ decl.sym.bind = CFREE_SB_GLOBAL;
+ decl.sym.visibility = CFREE_CG_VIS_DEFAULT;
+ sym = cfree_cg_decl(cg, decl);
+ EXPECT(sym != CFREE_CG_SYM_NONE, "[%s/O%d] decl failed", sh->name,
+ opt_level);
+
+ cfree_cg_func_begin(cg, sym);
+
+ memset(&attrs, 0, sizeof attrs);
+ attrs.name = cfree_sym_intern(c, "x");
+ param = cfree_cg_param(cg, 0, sh->selector_type, attrs);
+ EXPECT(param != CFREE_CG_LOCAL_NONE, "[%s/O%d] param failed", sh->name,
+ opt_level);
+
+ end_lbl = cfree_cg_label_new(cg);
+ default_lbl = sh->has_default ? cfree_cg_label_new(cg) : end_lbl;
+ if (sh->ncases) {
+ case_lbls = (CfreeCgLabel*)malloc(sh->ncases * sizeof *case_lbls);
+ cases = (CfreeCgSwitchCase*)malloc(sh->ncases * sizeof *cases);
+ EXPECT(case_lbls && cases, "[%s/O%d] alloc failed", sh->name, opt_level);
+ for (i = 0; i < sh->ncases; ++i) {
+ case_lbls[i] = cfree_cg_label_new(cg);
+ cases[i].value = (uint64_t)sh->values[i];
+ cases[i].label = case_lbls[i];
+ }
+ }
+
+ /* Push selector, dispatch. */
+ cfree_cg_push_local(cg, param);
+ cfree_cg_load(cg, (CfreeCgMemAccess){.type = sh->selector_type,
+ .align = cfree_cg_type_align(
+ c, sh->selector_type)});
+ memset(&sw, 0, sizeof sw);
+ sw.selector_type = sh->selector_type;
+ sw.default_label = default_lbl;
+ sw.cases = cases;
+ sw.ncases = sh->ncases;
+ sw.hint = sh->hint;
+ cfree_cg_switch(cg, sw);
+
+ /* Each arm: push result, jump to end_lbl. The cg API always materializes
+ * the return through `cfree_cg_ret` consuming the stack top; jump to a
+ * single ret epilogue at end_lbl. */
+ for (i = 0; i < sh->ncases; ++i) {
+ cfree_cg_label_place(cg, case_lbls[i]);
+ cfree_cg_push_int(cg, (uint64_t)(int64_t)sh->results[i], i32_ty);
+ cfree_cg_ret(cg);
+ }
+ if (sh->has_default) {
+ cfree_cg_label_place(cg, default_lbl);
+ cfree_cg_push_int(cg, (uint64_t)(int64_t)sh->default_result, i32_ty);
+ cfree_cg_ret(cg);
+ }
+ cfree_cg_label_place(cg, end_lbl);
+ cfree_cg_push_int(cg, (uint64_t)(int64_t)-1, i32_ty);
+ cfree_cg_ret(cg);
+ cfree_cg_func_end(cg);
+
+ EXPECT(g_fail == 0 || g_fail > 0, "shape-build sentinel"); /* no-op */
+
+ free(case_lbls);
+ free(cases);
+ cfree_cg_free(cg);
+ obj_free((ObjBuilder*)ob);
+}
+
+/* ---- Shapes ---------------------------------------------------------- */
+
+static void run_all_shapes(CfreeCompiler* c, CfreeCgTypeId i32_ty,
+ CfreeCgTypeId i64_ty, int opt_level) {
+ /* Dense unsigned range 10..15 + default. */
+ static const int64_t dense_vals[] = {10, 11, 12, 13, 14, 15};
+ static const int32_t dense_res[] = {100, 101, 102, 103, 104, 105};
+
+ /* Sparse: {1, 50, 1000}. */
+ static const int64_t sparse_vals[] = {1, 50, 1000};
+ static const int32_t sparse_res[] = {11, 22, 33};
+
+ /* Signed dense around zero: -3..3. */
+ static const int64_t signed_vals[] = {-3, -2, -1, 0, 1, 2, 3};
+ static const int32_t signed_res[] = {30, 31, 32, 33, 34, 35, 36};
+
+ /* Singleton. */
+ static const int64_t single_vals[] = {42};
+ static const int32_t single_res[] = {7};
+
+ SwitchShape shapes[] = {
+ /* TARGET_DEFAULT × shape × default-present/absent. */
+ {"dense_def_target", i32_ty, dense_vals, dense_res, 6, 1, 999,
+ CFREE_CG_SWITCH_TARGET_DEFAULT},
+ {"dense_nodef_target", i32_ty, dense_vals, dense_res, 6, 0, 0,
+ CFREE_CG_SWITCH_TARGET_DEFAULT},
+ {"sparse_def_target", i32_ty, sparse_vals, sparse_res, 3, 1, 99,
+ CFREE_CG_SWITCH_TARGET_DEFAULT},
+ {"signed_def_target", i32_ty, signed_vals, signed_res, 7, 1, 999,
+ CFREE_CG_SWITCH_TARGET_DEFAULT},
+ {"signed64_def_target", i64_ty, signed_vals, signed_res, 7, 1, 999,
+ CFREE_CG_SWITCH_TARGET_DEFAULT},
+
+ /* Forced hint variants. JUMP_TABLE is advisory; lowering may
+ * accept it or fall back to chain — both must produce a valid
+ * function. */
+ {"dense_def_jump_table", i32_ty, dense_vals, dense_res, 6, 1, 999,
+ CFREE_CG_SWITCH_JUMP_TABLE},
+ {"dense_nodef_jump_table", i32_ty, dense_vals, dense_res, 6, 0, 0,
+ CFREE_CG_SWITCH_JUMP_TABLE},
+ {"sparse_def_jump_table", i32_ty, sparse_vals, sparse_res, 3, 1, 99,
+ CFREE_CG_SWITCH_JUMP_TABLE},
+ {"signed_def_jump_table", i32_ty, signed_vals, signed_res, 7, 1, 999,
+ CFREE_CG_SWITCH_JUMP_TABLE},
+
+ {"dense_def_branch_chain", i32_ty, dense_vals, dense_res, 6, 1, 999,
+ CFREE_CG_SWITCH_BRANCH_CHAIN},
+ {"sparse_def_branch_chain", i32_ty, sparse_vals, sparse_res, 3, 1, 99,
+ CFREE_CG_SWITCH_BRANCH_CHAIN},
+
+ /* Singleton: minimum case count. Both hints. */
+ {"single_def_target", i32_ty, single_vals, single_res, 1, 1, 99,
+ CFREE_CG_SWITCH_TARGET_DEFAULT},
+ {"single_def_jump_table", i32_ty, single_vals, single_res, 1, 1, 99,
+ CFREE_CG_SWITCH_JUMP_TABLE},
+
+ /* Empty (default-only). cfree_cg_switch on an empty case array is
+ * tested for clean acceptance — frontends emit this from
+ * `switch (x) { default: ...; }`. */
+ {"empty_def_target", i32_ty, NULL, NULL, 0, 1, 7,
+ CFREE_CG_SWITCH_TARGET_DEFAULT},
+ };
+
+ size_t n = sizeof shapes / sizeof shapes[0];
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ build_switch_fn(c, i32_ty, &shapes[i], opt_level);
+ }
+}
+
+/* ---- Entry ----------------------------------------------------------- */
+
+int main(void) {
+ CfreeTarget target;
+ CfreeContext ctx;
+ CfreeCompiler* c = NULL;
+ CfreeCgBuiltinTypes bi;
+ CfreeCgTypeId i32_ty;
+ CfreeCgTypeId i64_ty;
+
+ memset(&target, 0, sizeof target);
+ target.arch = CFREE_ARCH_ARM_64;
+ target.os = CFREE_OS_LINUX;
+ target.obj = CFREE_OBJ_ELF;
+ target.ptr_size = 8;
+ target.ptr_align = 8;
+
+ memset(&ctx, 0, sizeof ctx);
+ ctx.heap = &g_heap;
+ ctx.file_io = NULL;
+ ctx.diag = &g_diag;
+ ctx.now = 0;
+
+ if (cfree_compiler_new(target, &ctx, &c) != CFREE_OK || !c) {
+ fprintf(stderr, "compiler_new failed\n");
+ return 2;
+ }
+
+ bi = cfree_cg_builtin_types(c);
+ i32_ty = bi.id[CFREE_CG_BUILTIN_I32];
+ i64_ty = bi.id[CFREE_CG_BUILTIN_I64];
+ EXPECT(i32_ty != CFREE_CG_TYPE_NONE, "i32 builtin id is none");
+ EXPECT(i64_ty != CFREE_CG_TYPE_NONE, "i64 builtin id is none");
+
+ run_all_shapes(c, i32_ty, i64_ty, /*opt_level=*/0);
+ run_all_shapes(c, i32_ty, i64_ty, /*opt_level=*/1);
+
+ cfree_compiler_free(c);
+ return g_fail ? 1 : 0;
+}
diff --git a/test/parse/cases/6_8_29_switch_signed_negative.c b/test/parse/cases/6_8_29_switch_signed_negative.c
@@ -0,0 +1,26 @@
+/* Signed selector with negative case values. A dense range around zero
+ * (-3..3) exercises the (sel - vmin) compute in a jump-table lowering
+ * where vmin is negative; the bounds check must use unsigned compare or
+ * sign-extension correctly. */
+static int pick(int x) {
+ switch (x) {
+ case -3: return 30;
+ case -2: return 31;
+ case -1: return 32;
+ case 0: return 33;
+ case 1: return 34;
+ case 2: return 35;
+ case 3: return 36;
+ default: return 99;
+ }
+}
+
+int test_main(void) {
+ int s = 0;
+ s += pick(-3); /* vmin -> 30 */
+ s += pick(3); /* vmax -> 36 */
+ s += pick(0); /* zero -> 33 */
+ s += pick(-4); /* below vmin -> 99 */
+ s += pick(4); /* above vmax -> 99 */
+ return s - 197; /* 30+36+33+99+99 - 197 = 100 */
+}
diff --git a/test/parse/cases/6_8_29_switch_signed_negative.expected b/test/parse/cases/6_8_29_switch_signed_negative.expected
@@ -0,0 +1 @@
+100
diff --git a/test/parse/cases/6_8_30_switch_sparse_chain.c b/test/parse/cases/6_8_30_switch_sparse_chain.c
@@ -0,0 +1,22 @@
+/* Sparse case values: three cases spread over a 1000-element range. The
+ * density-driven jump-table policy must reject this and keep a chain. The
+ * test pins correct behavior independent of which strategy was picked. */
+static int pick(int x) {
+ switch (x) {
+ case 1: return 11;
+ case 50: return 22;
+ case 1000: return 33;
+ default: return 99;
+ }
+}
+
+int test_main(void) {
+ int s = 0;
+ s += pick(1);
+ s += pick(50);
+ s += pick(1000);
+ s += pick(2);
+ s += pick(999);
+ s += pick(1001);
+ return s - 263; /* 11+22+33+99+99+99 - 263 = 100 */
+}
diff --git a/test/parse/cases/6_8_30_switch_sparse_chain.expected b/test/parse/cases/6_8_30_switch_sparse_chain.expected
@@ -0,0 +1 @@
+100
diff --git a/test/test.mk b/test/test.mk
@@ -124,16 +124,22 @@ $(AA64_ISA_TEST_BIN): test/arch/aa64_isa_test.c $(LIB_AR)
$(CC) $(DRIVER_CFLAGS) -Isrc test/arch/aa64_isa_test.c $(LIB_AR) -o $@
CG_API_TEST_BIN = build/test/cg_api_test
+CG_SWITCH_TEST_BIN = build/test/cg_switch_test
ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test
-test-cg-api: $(CG_API_TEST_BIN) $(ABI_CLASSIFY_TEST_BIN)
+test-cg-api: $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) $(ABI_CLASSIFY_TEST_BIN)
$(CG_API_TEST_BIN)
+ $(CG_SWITCH_TEST_BIN)
$(ABI_CLASSIFY_TEST_BIN)
$(CG_API_TEST_BIN): test/api/cg_type_test.c $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_CFLAGS) -Isrc test/api/cg_type_test.c $(LIB_AR) -o $@
+$(CG_SWITCH_TEST_BIN): test/api/cg_switch_test.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(DRIVER_CFLAGS) -Isrc test/api/cg_switch_test.c $(LIB_AR) -o $@
+
$(ABI_CLASSIFY_TEST_BIN): test/api/abi_classify_test.c $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(DRIVER_CFLAGS) -Isrc test/api/abi_classify_test.c $(LIB_AR) -o $@
diff --git a/test/toy/cases/125_switch_dense_boundaries.expected b/test/toy/cases/125_switch_dense_boundaries.expected
@@ -0,0 +1 @@
+312
diff --git a/test/toy/cases/125_switch_dense_boundaries.toy b/test/toy/cases/125_switch_dense_boundaries.toy
@@ -0,0 +1,35 @@
+// Dense case range with default; exercises the boundary selectors that a
+// jump-table lowering must bounds-check correctly: vmin, vmax, vmin-1,
+// vmax+1, an interior gap. Sum encodes which arm fired for each call.
+// At O0 today this lowers to a chain; at O1 (once density-based promotion
+// lands) it lowers to a table. Behavior must match.
+
+fn pick(x: i64): i64 {
+ return switch x {
+ 10 { 100 }
+ 11 { 101 }
+ 12 { 102 }
+ 13 { 103 }
+ 14 { 104 }
+ 15 { 105 }
+ // deliberate gap at 16
+ 17 { 107 }
+ 18 { 108 }
+ 19 { 109 }
+ 20 { 110 }
+ default { 999 }
+ };
+}
+
+fn __user_main(): i64 {
+ var s: i64 = 0;
+ s = s + pick(10); // vmin -> 100
+ s = s + pick(20); // vmax -> 110
+ s = s + pick(9); // vmin - 1 -> 999
+ s = s + pick(21); // vmax + 1 -> 999
+ s = s + pick(16); // interior gap -> 999
+ s = s + pick(15); // mid hit -> 105
+ return s - 3000; // 100+110+999+999+999+105 - 3000 = 312
+}
+
+fn main(): i32 { return __user_main() as i32; }
diff --git a/test/toy/cases/126_switch_sparse_chain.expected b/test/toy/cases/126_switch_sparse_chain.expected
@@ -0,0 +1 @@
+363
diff --git a/test/toy/cases/126_switch_sparse_chain.toy b/test/toy/cases/126_switch_sparse_chain.toy
@@ -0,0 +1,26 @@
+// Sparse case values: vmax - vmin = 999 with only 3 cases. Density
+// thresholds must reject the jump table at every opt level and stay on
+// the chain. Pins the no-promotion behavior so a tuning regression that
+// relaxes thresholds too far is caught.
+
+fn pick(x: i64): i64 {
+ return switch x {
+ 1 { 11 }
+ 50 { 22 }
+ 1000 { 33 }
+ default { 99 }
+ };
+}
+
+fn __user_main(): i64 {
+ var s: i64 = 0;
+ s = s + pick(1); // 11
+ s = s + pick(50); // 22
+ s = s + pick(1000); // 33
+ s = s + pick(2); // miss -> 99
+ s = s + pick(999); // miss -> 99
+ s = s + pick(1001); // miss -> 99
+ return s; // 11+22+33+99+99+99 = 363
+}
+
+fn main(): i32 { return __user_main() as i32; }
diff --git a/test/toy/cases/127_switch_forced_jump_table.expected b/test/toy/cases/127_switch_forced_jump_table.expected
@@ -0,0 +1 @@
+3019
diff --git a/test/toy/cases/127_switch_forced_jump_table.objdump b/test/toy/cases/127_switch_forced_jump_table.objdump
@@ -0,0 +1,2 @@
+__TEXT,__const
+.Lcfree_jt
diff --git a/test/toy/cases/127_switch_forced_jump_table.toy b/test/toy/cases/127_switch_forced_jump_table.toy
@@ -0,0 +1,40 @@
+// Forced .jump_table over a dense 16-case range with no default.
+// Stresses the table-emission path: when no default is given, every
+// in-range value must reach a case, and out-of-range values must fall
+// through past the switch (the synthesized default).
+
+fn pick(x: i64): i64 {
+ var r: i64 = -1;
+ switch @[.jump_table] x {
+ 0 { r = 1000; }
+ 1 { r = 1001; }
+ 2 { r = 1002; }
+ 3 { r = 1003; }
+ 4 { r = 1004; }
+ 5 { r = 1005; }
+ 6 { r = 1006; }
+ 7 { r = 1007; }
+ 8 { r = 1008; }
+ 9 { r = 1009; }
+ 10 { r = 1010; }
+ 11 { r = 1011; }
+ 12 { r = 1012; }
+ 13 { r = 1013; }
+ 14 { r = 1014; }
+ 15 { r = 1015; }
+ }
+ return r;
+}
+
+fn __user_main(): i64 {
+ var s: i64 = 0;
+ s = s + pick(0); // vmin in-range -> 1000
+ s = s + pick(15); // vmax in-range -> 1015
+ s = s + pick(7); // interior -> 1007
+ s = s + pick(-1); // below vmin -> -1 (synthesized default)
+ s = s + pick(16); // above vmax -> -1 (synthesized default)
+ s = s + pick(100); // far out -> -1 (synthesized default)
+ return s; // 1000+1015+1007 - 3 = 3019
+}
+
+fn main(): i32 { return __user_main() as i32; }