From 49464efdc5b79c0cd21f85c7f907dad5df12e44d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 15:01:53 +0900 Subject: [PATCH] test: port the amp-fn LEAQ observer to ww; retire 764_amp_fn_ident The five symmetric rows compile their r764_amp_fn_ident_* fixture sources in place (runtime owned by those fixtures; pre-fix junk-store still exits 0, so the LEAQ window is the discriminator) and keep the cs==ws byte-id leg. The cross-module row stays cstage-only pending #184; its wwstage/byte-id legs graduate with that task. --- Makefile | 3 +- test/asm/ampfn_test.ww | 127 ++++++++++ test/wcc/764_amp_fn_ident.c | 445 ------------------------------------ 3 files changed, 129 insertions(+), 446 deletions(-) create mode 100644 test/asm/ampfn_test.ww delete mode 100644 test/wcc/764_amp_fn_ident.c diff --git a/Makefile b/Makefile index 486461be..159e2c38 100644 --- a/Makefile +++ b/Makefile @@ -386,7 +386,8 @@ XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) # test-compiler beside the surviving residual carriers. ASM_WW_TESTS = test/asm/modshadow_test.ww test/asm/sret_test.ww \ test/asm/callarg_test.ww test/asm/chain_test.ww \ - test/asm/dataemit_test.ww test/asm/matchdispatch_test.ww + test/asm/dataemit_test.ww test/asm/matchdispatch_test.ww \ + test/asm/ampfn_test.ww ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%) # Ww-native assembler/linker OBJECT-level observers: single-file ww diff --git a/test/asm/ampfn_test.ww b/test/asm/ampfn_test.ww new file mode 100644 index 00000000..3a899bf8 --- /dev/null +++ b/test/asm/ampfn_test.ww @@ -0,0 +1,127 @@ +package ampfn_test; + +// Direct-w6c asm-window gate over `&fn` address-of lowering (#180). +// Port of the retired native carrier test/wcc/764_amp_fn_ident.c; +// every unowned assertion preserved. Runtime for the five symmetric +// rows is owned by the r764_amp_fn_ident_* fixtures (their sources +// are compiled here IN PLACE so fixture and window can never drift); +// pre-fix those fixtures still exit 0 on the junk store — the LEAQ +// presence below is the actual discriminator the corpus cannot own. +// +// Rows 1-5: the cstage .s must contain the row's `LEAQ (SB)` +// line (absent pre-#180: the cgen N_UN TK_AMP IDENT arm had no TY_FN +// branch, so the assign site stored stale AX junk), and cstage vs +// wwstage .s must be byte-identical (rule 10; the carrier compared +// driver __root.s — same emitter output, direct w6c form per the +// residual audit). +// +// cross_module: `&pkg.fn` over a real import tree. CSTAGE-ONLY — +// wwstage bails asserttyped on the N_UN TK_AMP N_DOT-mod-ident shape +// (open #184); its build/run and byte-id legs graduate when #184 +// lifts. The M1 path-mangled `LEAQ wcamffn764mod.somefn(SB)` and the +// build+run exit 0 are pinned; separate fresh trees keep the -S +// compile and the full build from sharing a stale sepwork. + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("ampfn FAIL: ", label, " -- ", why, "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (180i64 * (time.second: i64)): time.duration; +}; + +fn emit(td: str, label: str, stage: str, drv: str, src: str, + outname: str) void = { + let av: []str = [drv, "-o", strings.concat(td, "/", outname), src]; + let co: testenv.commandout; + testenv.runcommand(td, td, stage, av, tmo(), &co); + if (co.termination != exec.termination.EXIT || co.code != 0) { + fail(label, strings.concat(stage, " compile failed")); + }; +}; + +fn leaqrow(label: str, fixture: str, needle: str) void = { + let src: str = strings.concat(testenv.repo(), + "/test/wcc/data/r764_amp_fn_ident_", fixture, "/case.ww"); + let td: str = testenv.fresh(); + emit(td, label, "cstage", testenv.driver("w6c"), src, "cs.s"); + emit(td, label, "wwstage", testenv.driver("w6c_ww"), src, "ws.s"); + let cs: str = testenv.readfile(strings.concat(td, "/cs.s")); + let ws: str = testenv.readfile(strings.concat(td, "/ws.s")); + if (!testenv.has(cs, needle)) { + fail(label, strings.concat("missing `", needle, + "` in cstage .s (silent TK_AMP IDENT drop)")); + }; + if (!testenv.same(cs, ws)) { + fail(label, "cstage vs wwstage asm differs"); + }; + testenv.clean(td); +}; + +@test fn ampfnleaq() void = { + leaqrow("minimal", "minimal", "LEAQ\tmain.add1(SB)"); + leaqrow("branched_callee", "branched", "LEAQ\tmain.bb(SB)"); + leaqrow("alias_chain", "alias_chain", "LEAQ\tmain.add1(SB)"); + leaqrow("fn_with_args", "args", "LEAQ\tmain.many(SB)"); + leaqrow("fn_tuple_return", "tuple_return", "LEAQ\tmain.pair(SB)"); +}; + +fn xmodtree() str = { + let td: str = testenv.fresh(); + assert(os.mkdir(strings.concat(td, "/wcamffn764mod"), 493) == 0); + testenv.writefile( + strings.concat(td, "/wcamffn764mod/wcamffn764mod.ww"), + strings.concat( + "package wcamffn764mod;\n", + "export fn somefn(x: i32) i32 = { return x + 1; };\n")); + testenv.writefile(strings.concat(td, "/main764.ww"), strings.concat( + "package main;\n", + "import wcamffn764mod;\n", + "export fn main() i32 = {\n", + " let f = &wcamffn764mod.somefn;\n", + " return 0;\n", + "};\n")); + return td; +}; + +fn runcode(dir: str, name: str, argv: []str) i32 = { + let co: testenv.commandout; + testenv.runcommand(dir, dir, name, argv, tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +@test fn crossmodule() void = { + let tda: str = xmodtree(); + let av: []str = [testenv.driver("ww"), "build", "-S", "-o", + strings.concat(tda, "/main764"), "main764.ww"]; + if (runcode(tda, "compile", av) != 0) { + fail("cross_module", "cstage ww build -S failed"); + }; + let s: str = testenv.readfile( + strings.concat(tda, "/main764.sepwork/__root.s")); + if (!testenv.has(s, "LEAQ\twcamffn764mod.somefn(SB)")) { + fail("cross_module", + "missing path-mangled LEAQ wcamffn764mod.somefn(SB)"); + }; + testenv.clean(tda); + let tdb: str = xmodtree(); + let bav: []str = [testenv.driver("ww"), "build", "-o", + strings.concat(tdb, "/main764"), "main764.ww"]; + if (runcode(tdb, "build", bav) != 0) { + fail("cross_module", "cstage ww build failed"); + }; + let rav: []str = [strings.concat(tdb, "/main764")]; + if (runcode(tdb, "run", rav) != 0) { + fail("cross_module", "run-exit != 0"); + }; + testenv.clean(tdb); +}; diff --git a/test/wcc/764_amp_fn_ident.c b/test/wcc/764_amp_fn_ident.c deleted file mode 100644 index d78b11cb..00000000 --- a/test/wcc/764_amp_fn_ident.c +++ /dev/null @@ -1,445 +0,0 @@ -/* - * 764_amp_fn_ident — root-cause lock for project #180. Pre-fix master - * (caca68e baseline), the cgen N_UN TK_AMP arm fell through silently - * when the operand was an N_IDENT naming a top-level function: the - * arm had branches for off!=0 (local), let_islet, def_isstructdef/ - * arraydef/scalardef and a def_isanydef fatal, but NO TY_FN arm. The - * store at the assign site picked up whatever AX held from prior - * code (often a stale argument register), so `let f = &add1` wrote - * junk into f. A subsequent `(*f)(...)` then jumped through that - * junk and segfaulted. Sister-bug at cgen.c:2330 already had the - * TY_FN arm for the value-read of a bare ident (`let f = add1;` — - * though that ww-side spelling is rejected by the checker today); - * #180 adds the address-of twin. - * - * Fix: cmd/w6c/cgen.c N_UN TK_AMP IDENT inserts a TY_FN branch - * before the let/def cascade — `LEAQ mafn(opnd->str, c->cur_mod), AX` - * — mirror of the read-arm at line 2330. Selfhost twin in selfhost/ - * cmd/wcc/cgenexpr.ww cgun TK_AMP IDENT uses `fnretlookup(c, nm) != - * nil` as the analogous predicate (cstage tracks fn-ness via Type; - * wwstage tracks via the fnret registry — both stages resolve to - * the same LEAQ on byte-id). - * - * Phase 1 cross-mod probe (ken's mandate): `&pkg.fn` on CSTAGE - * already flows through the N_DOT TK_AMP branch (cgen.c:2477-2493) - * and emits the correct LEAQ via mafn(opnd->str, opnd->lhs->str) — - * verdict = FINE for cstage. WWSTAGE however bails asserttyped on - * the same shape (`un main.combined.ww:7`), a sibling checker gap - * filed as project #184 (wwstage N_UN TK_AMP N_DOT-mod-ident type_ - * stamp missing). Row 6 below therefore runs CSTAGE-ONLY to lock - * the working cstage behaviour; wwstage cross-mod is gated on the - * stage_mask field and skipped until #184 lifts the bail. - * - * Coverage (drew option (b) — exercise the address-of without the - * deref-call; runtime coverage for `(*f)(...)` deferred to project - * #181's probe, which lifts the wwstage asserttyped bail): - * 1. minimal — `let f = &add1` - * 2. branched callee — conditionally pick one of two fns - * 3. alias chain — `let f = &fn; let g = f` - * 4. fn-with-args — multiple scalar + ptr params - * 5. fn-with-tuple-return — (i64, str) return shape - * 6. cross-module — `let f = &pkg.fn` (Phase 1 = FINE) - * - * Runtime for the five symmetric rows now lives in r764_* fixtures. - * This artifact wrapper retains: - * a. cstage .s contains the expected `LEAQ (SB)` line — - * pre-fix that line is absent (silent drop). - * b. cstage .s == wwstage .s byte-identical — symmetry gate per - * CLAUDE.md rule 10. - * c. the cross-module C-stage-only runtime gate pending #184. - * - * GATE POLARITY: must stay GREEN. A red here means either the cgen - * TK_AMP IDENT TY_FN arm regressed, or stage symmetry drifted. - */ -#include -#include -#include -#include -#include -#include -#include -#include "wwtestpkg.h" - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -/* stage_mask bits — controls per-row which stages this fixture - * gates. Row 6 (cross_module) is cstage-only until project #184 - * lifts the wwstage asserttyped bail on `&mod.fn`. */ -#define STAGE_CS 1 -#define STAGE_WW 2 - -struct row { - const char *label; - /* Symmetric rows use their corpus source as the sole fixture body. */ - const char *fixture; - /* Source written into /.ww; for cross-mod - * (row 6) the secondary module lives at // - * .ww. modname/modsrc NULL for single-file rows. */ - const char *src; - const char *modname; - const char *modsrc; - /* expected_leaq: the LEAQ (SB) symbol expected in the - * cstage emitted .s. Pre-fix this line is absent on rows 1-5. - * Row 6 already worked via the N_DOT TK_AMP path. */ - const char *expected_leaq; - int stage_mask; -}; - -static const struct row rows[] = { - { "minimal", - "test/wcc/data/r764_amp_fn_ident_minimal/case.ww", - NULL, - NULL, NULL, - "LEAQ\tmain.add1(SB)", - STAGE_CS | STAGE_WW }, - { "branched_callee", - "test/wcc/data/r764_amp_fn_ident_branched/case.ww", - NULL, - NULL, NULL, - "LEAQ\tmain.bb(SB)", - STAGE_CS | STAGE_WW }, - { "alias_chain", - "test/wcc/data/r764_amp_fn_ident_alias_chain/case.ww", - NULL, - NULL, NULL, - "LEAQ\tmain.add1(SB)", - STAGE_CS | STAGE_WW }, - { "fn_with_args", - "test/wcc/data/r764_amp_fn_ident_args/case.ww", - NULL, - NULL, NULL, - "LEAQ\tmain.many(SB)", - STAGE_CS | STAGE_WW }, - { "fn_tuple_return", - "test/wcc/data/r764_amp_fn_ident_tuple_return/case.ww", - NULL, - NULL, NULL, - "LEAQ\tmain.pair(SB)", - STAGE_CS | STAGE_WW }, - /* Cross-mod: cstage emits the LEAQ via N_DOT TK_AMP (already - * working pre-#180). Wwstage bails asserttyped on the same - * shape — filed as #184; this row stays cstage-only until that - * lifts. M1 #22: `wcamffn764mod` is a directory package, so its - * exported `somefn` path-mangles to `wcamffn764mod.somefn` (the - * pre-M1 bare `somefn` is gone, mirroring 989_m1mangle_sym); the - * &-of hint routes through use_hint and the LEAQ matches the - * mangled definition. */ - { "cross_module", - NULL, - "import wcamffn764mod;\n" - "export fn main() i32 = {\n" - " let f = &wcamffn764mod.somefn;\n" - " return 0;\n" - "};\n", - "wcamffn764mod", - "package wcamffn764mod;\n" - "export fn somefn(x: i32) i32 = { return x + 1; };\n", - "LEAQ\twcamffn764mod.somefn(SB)", - STAGE_CS }, -}; - -static int cleanup_sources(const struct row *, const char *, const char *, int); - -/* write_sources — copies a migrated fixture into an isolated package-main - * root, or writes the one asymmetric cross-module row into that tree. */ -static int -write_sources(const struct row *r, char *src, size_t srcsz, - char *tmpdir, size_t tdsz, int seq, char *base_out, size_t basz) -{ - (void)seq; - int moddir_owned = 0; - snprintf(tmpdir, tdsz, "/tmp/wcamffn_XXXXXX"); - if (mkdtemp(tmpdir) == NULL) return -1; - snprintf(src, srcsz, "%s/main764.ww", tmpdir); - snprintf(base_out, basz, "main764"); - if (r->fixture != NULL) { - FILE *in = fopen(r->fixture, "rb"); - if (in == NULL) goto fail; - if (fseek(in, 0, SEEK_END) != 0) { fclose(in); goto fail; } - long n = ftell(in); - if (n < 0 || fseek(in, 0, SEEK_SET) != 0) { - fclose(in); - goto fail; - } - char *body = malloc((size_t)n + 1); - if (body == NULL) { fclose(in); goto fail; } - size_t got = fread(body, 1, (size_t)n, in); - int readerr = ferror(in); - fclose(in); - if (got != (size_t)n || readerr) { free(body); goto fail; } - body[got] = '\0'; - - FILE *out = fopen(src, "wb"); - if (out == NULL) { free(body); goto fail; } - wwtest_fputs(body, out); - int wr = ferror(out) ? -1 : 0; - free(body); - if (fclose(out) != 0) wr = -1; - if (wr != 0) goto fail; - return 0; - } - - if (r->modname != NULL) { - char moddir[256], modfile[256]; - snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname); - snprintf(modfile, sizeof modfile, "%s/%s.ww", - moddir, r->modname); - if (mkdir(moddir, 0755) != 0) goto fail; - moddir_owned = 1; - FILE *mf = fopen(modfile, "wb"); - if (!mf) goto fail; - wwtest_fputs(r->modsrc, mf); - int wr = ferror(mf) ? -1 : 0; - if (fclose(mf) != 0) wr = -1; - if (wr != 0) goto fail; - } - - FILE *f = fopen(src, "wb"); - if (!f) goto fail; - wwtest_fputs(r->src, f); - int wr = ferror(f) ? -1 : 0; - if (fclose(f) != 0) wr = -1; - if (wr != 0) goto fail; - return 0; - -fail: - if (cleanup_sources(r, tmpdir, "main764", moddir_owned) != 0) - fprintf(stderr, "amp_fn_ident: setup cleanup failed for %s\n", tmpdir); - return -1; -} - -static int -cleanup_sources(const struct row *r, const char *tmpdir, const char *base, - int moddir_owned) -{ - char p[512]; - int rc = 0; - snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); - if (unlink(p) != 0 && errno != ENOENT) rc = -1; - snprintf(p, sizeof p, "%s/%s", tmpdir, base); - if (unlink(p) != 0 && errno != ENOENT) rc = -1; - /* #93: the sep scratch dir + the tmpdir-owned outputs. */ - snprintf(p, sizeof p, "rm -rf %s/%s.sepwork", tmpdir, base); - if (runwait(p) != 0) rc = -1; - if (moddir_owned) { - char moddir[256]; - snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname); - snprintf(p, sizeof p, "%s/%s.ww", moddir, r->modname); - if (unlink(p) != 0 && errno != ENOENT) rc = -1; - if (rmdir(moddir) != 0 && errno != ENOENT) rc = -1; - } - if (rmdir(tmpdir) != 0) rc = -1; - return rc; -} - -/* compile_via_driver — runs compiler-only driver output in ; returns - * the compiler exit code without assembling or linking. */ -static int -compile_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - /* #93 sep layout: `-o ` emits the asm to - * .sepwork/__root.s (the post-flip layout); all outputs stay - * under tmpdir. */ - char cmd[2048]; - snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s build -S -o %s/main764 %s " - "2>/dev/null", tmpdir, driver, tmpdir, src); - return runwait(cmd); -} - -static int -build_via_driver(const char *driver, const char *tmpdir, const char *src) -{ - char stem[512], cmd[1280]; - snprintf(stem, sizeof stem, "%s", src); - char *dot = strrchr(stem, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - snprintf(cmd, sizeof cmd, - "cd %s && timeout 180 %s build -o %s %s " - "2>/dev/null", tmpdir, driver, stem, src); - return runwait(cmd); -} - -/* run_row — full build+run gate via a ww driver. Returns 0 if the - * binary builds and exits 0, else -1. */ -static int -run_row(const char *driver, const struct row *r, int seq) -{ - char src[256], tmpdir[256], base[64], outbin[512]; - if (write_sources(r, src, sizeof src, tmpdir, sizeof tmpdir, - seq, base, sizeof base) != 0) - return -1; - int rc = -1; - if (build_via_driver(driver, tmpdir, src) == 0) { - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - if (runwait(outbin) == 0) rc = 0; - } - if (cleanup_sources(r, tmpdir, base, r->modname != NULL) != 0) { - fprintf(stderr, "amp_fn_ident[%s]: cleanup failed\n", r->label); - rc = -1; - } - return rc; -} - -/* file_contains — true if file has at least one line containing - * . */ -static int -file_contains(const char *path, const char *needle) -{ - FILE *f = fopen(path, "rb"); - if (!f) return 0; - char buf[1024]; - int found = 0; - while (fgets(buf, sizeof buf, f) != NULL) { - if (strstr(buf, needle) != NULL) { found = 1; break; } - } - fclose(f); - return found; -} - -/* check_leaq — build the row via cstage, scan the emitted .s for the - * expected LEAQ line. Returns 0 if present, -1 otherwise. */ -static int -check_leaq(const char *driver, const struct row *r, int seq) -{ - char src[256], tmpdir[256], base[64], asmf[512]; - if (write_sources(r, src, sizeof src, tmpdir, sizeof tmpdir, - seq, base, sizeof base) != 0) - return -1; - int rc = -1; - if (compile_via_driver(driver, tmpdir, src) == 0) { - snprintf(asmf, sizeof asmf, "%s/%s.sepwork/__root.s", tmpdir, base); - if (file_contains(asmf, r->expected_leaq)) rc = 0; - } - if (cleanup_sources(r, tmpdir, base, r->modname != NULL) != 0) { - fprintf(stderr, "amp_fn_ident[%s]: cleanup failed\n", r->label); - rc = -1; - } - return rc; -} - -/* asm_byte_identical — diff cstage vs wwstage .s for the row. */ -static int -asm_byte_identical(const char *cdrv, const char *wdrv, - const struct row *r, int seq) -{ - char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512]; - if (write_sources(r, src, sizeof src, tdc, sizeof tdc, - seq, base, sizeof base) != 0) - return -1; - int rc = -1; - int have_tdw = 0; - if (compile_via_driver(cdrv, tdc, src) != 0) goto out; - snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); - - /* Build a parallel tree so each driver's explicitly retained - * .sepwork/__root.s remains available for comparison. */ - if (write_sources(r, src, sizeof src, tdw, sizeof tdw, - seq + 100000, base, sizeof base) != 0) - goto out; - have_tdw = 1; - if (compile_via_driver(wdrv, tdw, src) != 0) { - goto out; - } - snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - if (fc && fw) { - rc = 0; - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); -out: - if (have_tdw - && cleanup_sources(r, tdw, base, r->modname != NULL) != 0) { - fprintf(stderr, "amp_fn_ident[%s]: ww cleanup failed\n", r->label); - rc = -1; - } - if (cleanup_sources(r, tdc, base, r->modname != NULL) != 0) { - fprintf(stderr, "amp_fn_ident[%s]: c cleanup failed\n", r->label); - rc = -1; - } - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char cdrv[1024], wdrv[1024]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - int wwpresent = (access(wdrv, X_OK) == 0); - - int seq = 0; - - for (int i = 0; i < n; i++) { - /* Symmetric runtime rows are owned by r764_* fixtures. Keep - * only the cross-module C-stage-only runtime evidence here. */ - if ((rows[i].stage_mask & STAGE_CS) - && !(rows[i].stage_mask & STAGE_WW)) { - total++; - if (run_row(cdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "amp_fn_ident[cstage run][%s]: build/run failed\n", - rows[i].label); - fail++; - } - } - if (rows[i].stage_mask & STAGE_CS) { - total++; - if (check_leaq(cdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "amp_fn_ident[cstage leaq][%s]: missing `%s` in .s\n", - rows[i].label, rows[i].expected_leaq); - fail++; - } - } - if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { - total++; - if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) { - fprintf(stderr, - "amp_fn_ident[byte-id][%s]: cstage vs wwstage asm differs\n", - rows[i].label); - fail++; - } - } - } - - if (!wwpresent) - fprintf(stderr, "amp_fn_ident: skip wwstage (no %s)\n", wdrv); - - if (fail) { - fprintf(stderr, "amp_fn_ident: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("amp_fn_ident: %d/%d ok\n", total, total); - return 0; -}