Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 53 additions & 28 deletions bend2/comp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ type File = {
steps: Map<string, HTerm>;
opens: Map<string, boolean>;
srcs: Map<Name, Set<Name> | null>;
fids: Set<Name>;
effs: Map<string, [string, string]>;
loops: Map<Name, Name[]>;
flats: Map<Name, boolean>;
funs: Map<Name, Fun>;
Expand Down Expand Up @@ -1448,6 +1450,8 @@ function file_new(book: Bend.Book, js: boolean): File {
steps: new Map(),
opens: new Map(),
srcs: new Map(),
fids: new Set(),
effs: new Map(),
loops: new Map(),
flats: new Map(),
funs: new Map(),
Expand Down Expand Up @@ -1499,6 +1503,9 @@ function file_book(roots: Name[]): void {
for (const x of tld?.$ === "ADT" ? tld.c : tld ? [tld] : []) {
ty_srcs(x.T);
}
if (def_foreign(tld)) {
effect_src(d, tld);
}
if (!done_live(tld)) {
continue;
}
Expand Down Expand Up @@ -2829,38 +2836,56 @@ function emit_chain(sc: Scope, arms: [string, () => void][]): void {

// Hand-written C and JS name ids as CID(k) and FID(k): k in the source's
// namespace, else as is. An effect source is read once, in one namespace.
// effect_srcs splices a C source's backslash-newlines first, as C does even
// effect_src splices a C source's backslash-newlines first, as C does even
// in a comment; JS does not.

function c_ids(src: string, m = ""): string {
return src.replace(/\/\/.*|\/\*[^]*?\*\/|"(?:\\[^]|[^"\\\n])*"|'(?:\\[^]|[^'\\\n])*'|`(?:\\[^]|[^`\\])*`|\b([CF]ID)\(([\w./~-]+)\)/g, (t, p, k) => {
if (!p) {
const ID_WORDS = /\/\/.*|\/\*[^]*?\*\/|"(?:\\[^]|[^"\\\n])*"|'(?:\\[^]|[^'\\\n])*'|`(?:\\[^]|[^`\\])*`|(?<![$\u200c\u200d\p{ID_Continue}])([CF]ID)\(([\w./~-]+)\)/gu;

function c_ids(src: string, m = "", use?: (p: string, k: Name) => void): string {
const replace = (t: string, p: string | undefined, k: string | undefined, at: number): string => {
// '#' starts a private JS name, but C's ## may paste an ID macro.
if (!p || !k || (FL.js && src[at - 1] === "#")) {
return t;
}
const q = [m === "" ? k : m + ":" + k, k].find((q) => q in FL.book.ctrs
|| q in FL.book.tlds || FL.ids.has(p + "_" + q))
?? die(`${p}(${k}) names no constructor or def`);
|| q in FL.book.tlds || FL.ids.has(p + "_" + q));
if (use) {
if (q !== undefined) use(p, q);
return t;
}
if (q === undefined) die(`${p}(${k}) names no constructor or def`);
return FL.js ? JSON.stringify(p === "CID" && q in FL.book.ctrs
? js_tag(q) : q) : name_id(p + "_", q);
});
}

function effect_srcs(ext: string, miss: string): string[] {
const seen = new Map<string, string>();
for (const [k, tld] of done_defs(def_foreign)) {
const path = fs.realpathSync(tld.i!.find((x) => x.endsWith(ext))
?? die(miss + Bend.name_key(k)));
const m = tld.m ?? "";
if ((seen.get(path) ?? m) !== m) {
die(path + " is imported from two namespaces, '" + seen.get(path)
+ "' and '" + m + "'");
}
seen.set(path, m);
};
if (use) {
for (const hit of src.matchAll(ID_WORDS)) replace(hit[0], hit[1], hit[2], hit.index);
return src;
}
return src.replace(ID_WORDS, replace);
}

function effect_src(k: Name, tld: Bend.Def & { i: string[] }): void {
const ext = FL.js ? ".js" : ".c";
const miss = FL.js ? "a foreign def without a .js import: " : "no .c import: ";
const path = fs.realpathSync(tld.i.find((x) => x.endsWith(ext))
?? die(miss + Bend.name_key(k)));
const m = tld.m ?? "";
const seen = FL.effs.get(path);
if (seen && seen[0] !== m) {
die(path + " is imported from two namespaces, '" + seen[0] + "' and '" + m + "'");
}
if (!seen) {
const raw = fs.readFileSync(path, "utf8");
const src = FL.js ? raw : raw.replace(/\\[ \t]*\r?\n/g, "");
FL.effs.set(path, [m, src]);
if (!FL.js) c_ids(src, m, (p, q) => {
const d = FL.book.tlds[q];
if (p === "FID" && fun_runs(d)) {
src_add(q);
FL.fids.add(q);
}
});
}
return [...seen].map(([p, m]) => {
const src = fs.readFileSync(p, "utf8");
return c_ids(ext === ".c" ? src.replace(/\\[ \t]*\r?\n/g, "") : src, m);
});
}

// C
Expand Down Expand Up @@ -2911,7 +2936,7 @@ export function compile_book(book: Bend.Book): string {
const edges = [...FL.segs, ...FL.spins].flatMap((s) =>
[...s.refs].map((r) => [s.fid, r]));
const reach = (from: string[]) => graph_close(new Set(from), edges);
const live = reach([seg_fid("main")]);
const live = reach(["main", ...FL.fids].map(seg_fid));
const wide = [...FL.bangs].some((k) => fun_of(k).live.some(([, , A]) =>
ty_holds(A, (t) => !ty_tag(t, "ADT Typ Qua Min Eql"))));
const dev = reach([...[...FL.bangs].map(seg_fid), ...wide ? FL.clos : []]);
Expand Down Expand Up @@ -2990,7 +3015,7 @@ export function compile_book(book: Bend.Book): string {
.join("\n");
}).join("\n\n");
const c = c_ids(runtime_c([tabs, ...desc].join("\n\n"), spins, segs,
effect_srcs(".c", "no .c import: ").join("")));
[...FL.effs.values()].map(([m, src]) => c_ids(src, m)).join("")));
// keep nothing memoized: clang may run next, while this process waits
FL = file_new(book, false);
return c;
Expand Down Expand Up @@ -3278,8 +3303,8 @@ export function js_lib(book: Bend.Book, mod = false): string {
memo_gc();
js_def(sc, k, def);
}
const effs = effect_srcs(".js", "a foreign def without a .js import: ")
.map((t) => `(() => {\n${t}\n})();\n\n`).join("");
const effs = [...FL.effs.values()]
.map(([m, src]) => `(() => {\n${c_ids(src, m)}\n})();\n\n`).join("");
const lib = outs === null ? "" : `export default {\n${outs.map((k) => {
const { live, lays } = fun_of(k);
const ps = live.map((_, i) => "a" + i);
Expand Down
16 changes: 16 additions & 0 deletions tests/io/effect_identifier_boundary.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# CID is a macro token, not a suffix of a legal host identifier.
import Base

def answer() -> IO(U32):
import "./effect_identifier_boundary.c"
import "./effect_identifier_boundary.js"

def helper() -> IO(U32):
import "./effect_identifier_boundary.c"

def main() -> IO(Unit):
do IO<Unit>:
value : U32 <- answer()
IO.print(U32.show(value))

#|42
27 changes: 27 additions & 0 deletions tests/io/effect_identifier_boundary.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
static Term $CID(Term n) { return n + 1; }
static Term πCID(Term n) { return n + 1; }

#define ID_OF(x) x##CID(answer)

static Term answer_run(Env e, Term* f, IoWork* w) {
(void)e;
(void)f;
(void)w;
return $CID(20) + πCID(20);
}

static void __attribute__((constructor)) answer_use(void) {
io_eff(ID_OF(), answer_run);
}

#ifdef CID(helper)
static Term helper_run(Env e, Term* f, IoWork* w) {
(void)e;
(void)f;
(void)w;
return 42;
}
static void __attribute__((constructor)) helper_use(void) {
io_eff(CID(helper), helper_run);
}
#endif
12 changes: 12 additions & 0 deletions tests/io/effect_identifier_boundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function $CID(n) { return n + 1; }
function πCID(n) { return n + 1; }
function joined‍CID(n) { return n + 1; }
class Counter {
#CID(n) { return n + 1; }
value() { return this.#CID(10); }
}
function answer() {
if (FID(helper) !== "helper") throw new Error("wrong foreign function name");
return $CID(9) + πCID(10) + joined‍CID(9) + new Counter().value();
}
io_eff(CID(answer), answer);
21 changes: 21 additions & 0 deletions tests/io/foreign_fused_fid.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The single Bend call must not fuse away an entry also used by FID.
import Base

def hidden(n: U32) -> U32:
U32.inc(n)

def caller(n: U32) -> U32:
hidden(n)

def answer() -> IO(U32):
import "./foreign_fused_fid.c"
import "./foreign_fused_fid.js"

def main() -> IO(Unit):
do IO<Unit>:
IO.print(U32.show(caller(41)))
value : U32 <- answer()
IO.print(U32.show(value))

#|42
#|42
13 changes: 13 additions & 0 deletions tests/io/foreign_fused_fid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
static Term answer_run(Env e, Term* f, IoWork* w) {
(void)f;
(void)w;
u64 at = task_node(e, FID(hidden), TERM_HOLE, 0, 0);
e.mem[at] = 41;
Term result = corpus_eval(e.mem, term_tsk(FID(hidden), at));
if (err_seen(e.mem)) err_fail("fused function entry failed");
return result;
}

static void __attribute__((constructor)) answer_use(void) {
io_eff(CID(answer), answer_run);
}
4 changes: 4 additions & 0 deletions tests/io/foreign_fused_fid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function answer() {
return 41 + 1;
}
io_eff(CID(answer), answer);
16 changes: 16 additions & 0 deletions tests/io/foreign_only_fid.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# A C effect calls a function that has no Bend callsite.
import Base

def hidden(n: U32) -> U32:
U32.inc(n)

def answer() -> IO(U32):
import "./foreign_only_fid.c"
import "./foreign_only_fid.js"

def main() -> IO(Unit):
do IO<Unit>:
value : U32 <- answer()
IO.print(U32.show(value))

#|42
13 changes: 13 additions & 0 deletions tests/io/foreign_only_fid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
static Term answer_run(Env e, Term* f, IoWork* w) {
(void)f;
(void)w;
u64 at = task_node(e, FID(hidden), TERM_HOLE, 0, 0);
e.mem[at] = 41;
Term result = corpus_eval(e.mem, term_tsk(FID(hidden), at));
if (err_seen(e.mem)) err_fail("foreign-only function failed");
return result;
}

static void __attribute__((constructor)) answer_use(void) {
io_eff(CID(answer), answer_run);
}
4 changes: 4 additions & 0 deletions tests/io/foreign_only_fid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function answer() {
return 41 + 1;
}
io_eff(CID(answer), answer);