diff --git a/Makefile b/Makefile index 436c647c..772b6f47 100644 --- a/Makefile +++ b/Makefile @@ -420,7 +420,7 @@ MISC_WW_TARGETS = $(MISC_WW_TESTS:%=wwtest/%) # wide-rune round-trips, the c6 sep soak). Compiler/driver gates like # test/sep: they run under test-compiler. TOOL_WW_TESTS = test/tool/rejects_test.ww test/tool/driver_test.ww \ - test/tool/wwdump_test.ww + test/tool/wwdump_test.ww test/tool/ffi_test.ww TOOL_WW_TARGETS = $(TOOL_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/tool/ffi_test.ww b/test/tool/ffi_test.ww new file mode 100644 index 00000000..4158029f --- /dev/null +++ b/test/tool/ffi_test.ww @@ -0,0 +1,92 @@ +package ffi_test; + +// @symbol FFI codegen observers. Port of the retired native carrier +// test/wcc/800_ffi.c; every assertion preserved, two strengthened. +// +// symbolredirect — @symbol("cout") on a body-less fn redirects the +// CALL at the codegen layer: the emitted .s must reference the +// external name (`CALL\tcout(SB)`) and must NOT reference the ww-side +// ident (`CALL\twriteit(SB)`). The C carrier scraped cstage w6c +// stdout only, with no exit-code check; the port compiles via -o on +// BOTH frontends, gates the exits, and adds the rule-10 cs==ww +// byte-id (residual-carrier-audit.json 800 port note). The inline +// source gains an explicit `package main;`: the carrier fed it +// through wwtest_fputs, which prepended the clause. +// +// libcsmoke — lib/c/libc/libc.ww (the real libc binding module, +// body-less fns, no TEXT directives) parses+checks+cgens to exit 0; +// strengthened from cstage-only to both frontends. Not wired as a +// LIBRARY_STANDALONE_SOURCE because this observer owns the claim. + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("ffi FAIL: ", label, " -- ", why, "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (60i64 * (time.second: i64)): time.duration; +}; + +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 symbolredirect() void = { + let td: str = testenv.fresh(); + let src: str = strings.concat(td, "/ffi.ww"); + testenv.writefile(src, strings.concat( + "package main;\n", + "@symbol(\"cout\") fn writeit(b: *u8, n: u64) i64;\n", + "fn main() i32 = { writeit(nil, 0); return 0; };\n")); + let comps: []str = ["w6c", "w6c_ww"]; + let asms: []str = ["", ""]; + let c: i32 = 0; + for (c < 2) { + let asmf: str = strings.concat(td, "/ffi.", comps[c], ".s"); + let av: []str = [testenv.driver(comps[c]), src, "-o", asmf]; + if (runcode(td, strings.concat("cc_", comps[c]), av) != 0) { + fail("symbolredirect", strings.concat(comps[c], + " rejected the @symbol declaration")); + }; + asms[c] = testenv.readfile(asmf); + c += 1; + }; + if (!testenv.has(asms[0], "CALL\tcout(SB)")) { + fail("symbolredirect", "missing CALL cout(SB) in the cstage .s"); + }; + if (testenv.has(asms[0], "CALL\twriteit(SB)")) { + fail("symbolredirect", + "call emitted by ident name, not @symbol name"); + }; + if (!testenv.same(asms[0], asms[1])) { + fail("symbolredirect", "cs .s != ww .s (rule 10)"); + }; + testenv.clean(td); +}; + +@test fn libcsmoke() void = { + let td: str = testenv.fresh(); + let libc: str = strings.concat(testenv.repo(), "/lib/c/libc/libc.ww"); + let comps: []str = ["w6c", "w6c_ww"]; + let c: i32 = 0; + for (c < 2) { + let av: []str = [testenv.driver(comps[c]), libc, "-o", + strings.concat(td, "/libc.", comps[c], ".s")]; + if (runcode(td, strings.concat("smoke_", comps[c]), av) != 0) { + fail("libcsmoke", strings.concat(comps[c], + " failed on lib/c/libc/libc.ww")); + }; + c += 1; + }; + testenv.clean(td); +}; diff --git a/test/wcc/800_ffi.c b/test/wcc/800_ffi.c deleted file mode 100644 index c0eb0861..00000000 --- a/test/wcc/800_ffi.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 800_ffi — verify the @symbol("X") attribute redirects CALLs at - * the codegen layer. The ww source declares a body-less fn with an - * external symbol name, calls it; the emitted .s must reference the - * external name, not the ww-side ident. - * - * Also smoke-tests building lib/c/libc/libc.ww (parse + check + cgen - * succeed; nothing to run). - */ -#include -#include -#include -#include -#include "wwtestpkg.h" - -static char * -run6c(const char *bin, const char *src) -{ - char path[64]; - snprintf(path, sizeof path, "/tmp/wwffi_%d.ww", getpid()); - FILE *f = fopen(path, "wb"); - wwtest_fputs(src, f); - fclose(f); - - char cmd[512]; - snprintf(cmd, sizeof cmd, "%s/w6c %s 2>&1", bin, path); - FILE *p = popen(cmd, "r"); - size_t cap = 4096, n = 0; - char *buf = malloc(cap); - int c; - while ((c = fgetc(p)) != EOF) { - if (n + 1 >= cap) { cap *= 2; buf = realloc(buf, cap); } - buf[n++] = (char)c; - } - buf[n] = '\0'; - pclose(p); - unlink(path); - return buf; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - - int fail = 0; - - /* @symbol redirects ident name to symbol name */ - { - char *out = run6c(bin, - "@symbol(\"cout\") fn writeit(b: *u8, n: u64) i64;\n" - "fn main() i32 = { writeit(nil, 0); return 0; };"); - if (strstr(out, "CALL\tcout(SB)") == NULL) { - fprintf(stderr, "ffi: missing CALL cout(SB) in:\n%s\n", out); - fail++; - } - if (strstr(out, "CALL\twriteit(SB)") != NULL) { - fprintf(stderr, "ffi: should not call by ident name:\n%s\n", out); - fail++; - } - free(out); - } - - /* lib/c/libc parses + cgens (no defined fns means no TEXT directives) */ - { - char path[256]; - snprintf(path, sizeof path, "%s/../../lib/c/libc/libc.ww", bin); - char cmd[512]; - snprintf(cmd, sizeof cmd, "%s/w6c %s > /dev/null 2>&1", bin, path); - int rc = system(cmd); - if (rc != 0) { - fprintf(stderr, "ffi: w6c failed on libc.ww (rc=%d)\n", rc); - fail++; - } - } - - if (fail) return 1; - puts("ffi: ok"); - return 0; -}