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
5 changes: 4 additions & 1 deletion .build/dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
(cmdliner (>= 1.2))
(yojson (>= 2.1))
(alcotest (and (>= 1.7) :with-test))
(odoc (and (>= 2.4) :with-doc)))
(odoc (and (>= 2.4) :with-doc))
(js_of_ocaml (>= 5.0))
(js_of_ocaml-ppx (>= 5.0))
(ocamlformat (and (>= 0.26) :with-test)))
(tags
(programming-language compiler webassembly affine-types dependent-types row-polymorphism effects ocaml)))
6 changes: 4 additions & 2 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,19 @@ Both are FOSS with independent governance (no Big Tech).

### TypeScript Exemptions (Approved)

The "no new TypeScript" rule has nine approved exemptions in this repo. These paths are *not* policy violations — they are documented carve-outs because the file format or downstream consumer requires TypeScript:
The "no new TypeScript" rule has eight approved exemptions in this repo. These paths are *not* policy violations — they are documented carve-outs because the file format or downstream consumer requires TypeScript:

| Path | Files | Rationale | Unblock condition |
|---|---|---|---|
| `packages/affine-js/types.d.ts` | 1 | TypeScript declaration file — the public API contract by which JS callers consume AffineScript-compiled artefacts. `.d.ts` is TS by definition. | Generate from canonical compiler output (issue: see ROADMAP). |
| `packages/affine-ts/types.d.ts` | 1 | Same, for TS callers. | Same as above. |
| `editors/vscode/src/extension.ts` | 1 | VS Code extension entry point. Path pinned by `package.json`'s `main` field. | AffineScript issue #35 (Node-target codegen). |
| `affinescript-deno-test/*.ts` | 6 | Deno-based test harness for AffineScript itself: `cli.ts`, `mod.ts`, `lib/{compile,discover,runner}.ts`, `example/smoke_driver.ts`. Deno test runner is TS-native. | AffineScript stdlib + Deno bindings (no scheduled issue). |

Adding to this list requires explicit user approval and an unblock condition. New TypeScript files outside this list are still banned per the policy table above.

**Closed exemptions:**
- `editors/vscode/src/extension.ts` — closed 2026-05-03 by issue #35 Phase 3 (Node-target codegen + Vscode bindings landed). Source of truth is now `editors/vscode/src/extension.affine`, compiled to `out/extension.cjs`. Re-introducing the .ts file is blocked by `tools/check-no-extension-ts.sh` (wired into `just check` and `.github/workflows/ci.yml`).

The 5 external references to `affinescript-deno-test/` (CI workflow, status docs, history docs) and the 3 references to `packages/affine-{js,ts}/` (status docs, Deno config) are why physical relocation into a `vendor/` subtree was rejected — the relocation cost exceeded the visibility benefit when the directories are already named clearly.

### Package Management
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ jobs:
- name: Run face-transformer regression tests
run: opam exec -- ./tools/run_face_transformer_tests.sh

- name: Issue #35 Phase 3 — block extension.ts regression
# The vscode extension is now authored in extension.affine and
# compiled to extension.cjs. Re-introducing extension.ts would
# silently drift the source-of-truth back to TypeScript, which
# the policy forbids. See tools/check-no-extension-ts.sh for
# rationale + recovery instructions.
run: ./tools/check-no-extension-ts.sh

- name: Check formatting
run: opam exec -- dune build @fmt

Expand Down
16 changes: 13 additions & 3 deletions .machine_readable/6a2/STATE.a2ml

Large diffs are not rendered by default.

155 changes: 101 additions & 54 deletions bin/main.ml

Large diffs are not rendered by default.

37 changes: 30 additions & 7 deletions docs/guides/frontier-programming-practices/AI.a2ml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@
"ONNX/MLIR/TFLite → onnx_codegen.ml"
"Verilog/VHDL → verilog_codegen.ml"))

(use-shared-kernel-sublang-module
(rule "Every Tier-C kernel sublanguage backend opens [Kernel_sublang] and reuses its primitives instead of inlining its own. The module provides: the [Unsupported] exception (one shared type, not per-backend), [pick_entry ?names], [strip_ownership], [is_unit_ty], [array_element], [require_array_element], [math_builtins], [is_math_builtin], [validate_return], [validate_params], and [validate_compute_kernel_shape] (the canonical 'first param Int, rest Array buffers, returns Unit' predicate used by WGSL/SPIR-V/CUDA/Metal/OpenCL).")
(location "lib/kernel_sublang.ml")
(extend-not-fork "If a new Tier-C backend needs an intrinsic the shared math_builtins list lacks, add it to that list rather than maintaining a parallel allowlist. If a new validation rule emerges that two or more backends need, factor it into Kernel_sublang rather than copying."))

(validate-end-to-end
(rule "Every shipped backend must round-trip through the target's reference toolchain. Bytes a human read are not bytes that work. The honest MVP claim is 'the bytes round-trip,' not 'the bytes look right.'")
(validators "naga (WGSL), faust (Faust), oxionnx-proto (ONNX), cc (C), rustc (Rust), ocaml (OCaml), lua5.4 (Lua), bash -n (Bash), nickel typecheck (Nickel), llc (LLVM), node/deno (JS)")
Expand All @@ -324,13 +329,30 @@
(when-adding-backend-N+1
(step (n 1) (do "identify the tier (A/B/C above); pick the template emitter from the tier's template-pairs list"))
(step (n 2) (do "create lib/<name>_codegen.ml as a clone with target-specific lowering rules; reuse mangle / type-mapping / gen_lit shape"))
(step (n 3) (do "register the module in lib/dune (alphabetical) and add `.<ext>` dispatch in bin/main.ml (both --json and human branches)"))
(step (n 4) (do "build: `eval $(opam env --switch=default) && dune build lib bin/main.exe`"))
(step (n 5) (do "validate end-to-end with the target's reference toolchain on /tmp/cdemo.affine (the canonical Int/branch/let test); record the result in the recap"))
(step (n 6) (do "for tier-C kernel backends: also test Float arithmetic, since the typechecker patch landed 2026-05-02"))
(step (n 7) (do "document any frontend gap surfaced by the work in this AI.a2ml file under (backends → known-limitations); fix it BEFORE the next adaptive task per the corrective-before-adaptive ordering rule")))
(step (n 3) (do "for Tier-C backends: open Kernel_sublang and use its shared primitives (pick_entry / strip_ownership / array_element / math_builtins / Unsupported) instead of inlining your own"))
(step (n 4) (do "register the module in lib/dune (alphabetical) and add `.<ext>` dispatch in bin/main.ml (both --json and human branches)"))
(step (n 5) (do "build: `eval $(opam env --switch=default) && dune build lib bin/main.exe`"))
(step (n 6) (do "validate end-to-end with the target's reference toolchain on /tmp/cdemo.affine (the canonical Int/branch/let test); record the result in the recap"))
(step (n 7) (do "for tier-C kernel backends: also test Float arithmetic, since the typechecker patch landed 2026-05-02"))
(step (n 8) (do "for Tier-A backends: also test the Phase-4 trio (tuple p4a, record p4b, variant+match p4c) end-to-end; emit `Unsupported` loudly for any shape outside scope"))
(step (n 9) (do "document any frontend gap surfaced by the work in this AI.a2ml file under (backends → known-limitations); fix it BEFORE the next adaptive task per the corrective-before-adaptive ordering rule")))

(known-limitations
(limitation
(name "wasm-stdin-and-exit-propagation")
(since "0.1.0")
(resolved "partial — 2026-05-03")
(impact
"WASM Phase 6 is now end-to-end on wasmtime — println of string literals works for any number of calls; the heap-alignment trap that surfaced after the first println was an iovec-layout bug in [wasi_runtime.gen_println] (1-byte newline placed before a 4-byte iovec, causing an unaligned i32.store at temp+1). Fixed by reordering the layout so the iovec sits at temp+0 (aligned) and the newline byte moves to temp+12. Total allocation rounded from 13 to 16 bytes so subsequent allocs stay aligned.")
(deferred
"Three pieces remain for a fully Phase-6-and-7 WASM backend, all in [lib/wasi_runtime.ml] / [lib/codegen.ml]:
(1) [fd_read] import + [gen_read_line] helper — mirror [create_fd_write_import] / [gen_print_str]; the line-reader needs a byte-loop because [fd_read] returns N bytes, not lines (~80 lines of wasm IR);
(2) Exit-code propagation — replace the [(start $main)] directive with an exported [_start] that calls [main] then [proc_exit(retval)] via a [wasi_snapshot_preview1.proc_exit] import. Side effect: wasmtime runs without [--invoke main] and exit codes flow naturally;
(3) Real [OpConcat] (currently a placeholder [I32Add]) — needs allocation + byte-copy. Without [I32Load8U]/[I32Store8] in [lib/wasm.ml]'s instruction set the byte loop is awkward; consider adding bulk-memory ([memory.copy], opcode 0xfc 0x0a) to the AST first.
Estimated scope: 2-3 hours of focused WASM work in the 1900-line backend, additive to the existing helper structure.")
(recommendation
"Tackle exit-code propagation first — it's the smallest of the three (one new import, swap [(start)] for [(_start)], propagate a single i32 to proc_exit) and unblocks running WASM programs without the [--invoke main] flag. Then add [fd_read]/[gen_read_line]. [OpConcat] last; consider extending [wasm.ml] with [MemoryCopy] before writing the helper."))

(limitation
(name "no-target-intrinsic-protocol")
(since "0.1.0")
Expand All @@ -346,7 +368,8 @@
(limitation
(name "kernel-sublangs-share-no-validator")
(since "0.1.0")
(impact "WGSL, Faust, ONNX each have their own restriction predicate. A shared 'kernel-sublanguage validator' module would let backends declare their accepted subset declaratively. Perfective task.")
(recommendation "If two new tier-C backends ship with overlapping restrictions, factor the validator before adding the third.")))
(resolved "0.1.1 — 2026-05-03")
(impact "Originally each Tier-C backend kept its own restriction predicate; resolved by factoring the shared primitives into [lib/kernel_sublang.ml]. ~140 lines of duplicated boilerplate removed across 7 backends; the module exposes Unsupported, pick_entry, strip_ownership, is_unit_ty, array_element, require_array_element, math_builtins, validate_compute_kernel_shape, etc.")
(recommendation "Use Kernel_sublang for any new Tier-C backend; extend the module rather than fork its rules.")))
)
)
96 changes: 96 additions & 0 deletions editors/vscode/out/extension.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Generated by AffineScript. Do not edit.
// Node-CJS shim wrapping the compiled .wasm module so VS Code's extension
// host (or any CJS consumer) can require() it directly.
//
// Issue #35 Phase 1 — no vscode API bindings yet (that's Phase 2 via
// stdlib/Vscode.affine). The Wasm module's `activate` and `deactivate`
// exports become exports.activate / exports.deactivate.

"use strict";

const _wasmBase64 = "AGFzbQEAAAABWhFgBH9/f38Bf2ACf38Bf2ABfwF/YAN/f38Bf2AAAX9gBX9/f39/AX9gAn9/AX9gAX8Bf2ACf38Bf2AAAX9gAAF/YAABf2AAAX9gAAF/YAABf2ABfwF/YAABfwL9BBYWd2FzaV9zbmFwc2hvdF9wcmV2aWV3MQhmZF93cml0ZQAABlZzY29kZQ9yZWdpc3RlckNvbW1hbmQAAQZWc2NvZGUQZ2V0Q29uZmlndXJhdGlvbgACBlZzY29kZRZ3b3Jrc3BhY2VDb25maWdHZXRCb29sAAMGVnNjb2RlGHdvcmtzcGFjZUNvbmZpZ0dldFN0cmluZwADBlZzY29kZRBzaG93RXJyb3JNZXNzYWdlAAIGVnNjb2RlEnNob3dXYXJuaW5nTWVzc2FnZQACBlZzY29kZRZzaG93SW5mb3JtYXRpb25NZXNzYWdlAAIGVnNjb2RlDmNyZWF0ZVRlcm1pbmFsAAIGVnNjb2RlDHRlcm1pbmFsU2hvdwACBlZzY29kZRB0ZXJtaW5hbFNlbmRUZXh0AAEGVnNjb2RlEHB1c2hTdWJzY3JpcHRpb24AAQZWc2NvZGUUZWRpdG9yQWN0aXZlRmlsZVBhdGgABAZWc2NvZGUWZWRpdG9yQWN0aXZlTGFuZ3VhZ2VJZAAEBlZzY29kZQpjb25zb2xlTG9nAAIGVnNjb2RlCGV4ZWNTeW5jAAIGVnNjb2RlDHN0cmluZ0NvbmNhdAABBlZzY29kZQ5zdHJpbmdFbmRzV2l0aAABBlZzY29kZRNzdHJpbmdSZXBsYWNlU3VmZml4AAMUVnNjb2RlTGFuZ3VhZ2VDbGllbnQRbmV3TGFuZ3VhZ2VDbGllbnQABRRWc2NvZGVMYW5ndWFnZUNsaWVudBNsYW5ndWFnZUNsaWVudFN0YXJ0AAIUVnNjb2RlTGFuZ3VhZ2VDbGllbnQSbGFuZ3VhZ2VDbGllbnRTdG9wAAIDDAsGBwgJCgsMDQ4PEAQBAAUDAQABBgEAB3oIBm1lbW9yeQIADWhhbmRsZXJfY2hlY2sAGQxoYW5kbGVyX2V2YWwAGg9oYW5kbGVyX2NvbXBpbGUAGw5oYW5kbGVyX2Zvcm1hdAAcE2hhbmRsZXJfcmVzdGFydF9sc3AAHQhhY3RpdmF0ZQAfCmRlYWN0aXZhdGUAIAkBAArZBAscAQF/IAAQCCECIAIQCRogAiABEAoaQQAPGkEACykBAX8QDSEBIAFBgBAQEUEBRgR/EAwPGkEABUGQEBAFGkGtEA8aQQALCyEBAX9BsRAgABAQQcIQEBAhAiACIAEQEEHIEBAQDxpBAAsyAQF/Qc0QEBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaQeEQQc0QIAAQGBAWDxpBAAsyAQF/QfcQEBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaQf8QQfcQIAAQGBAWDxpBAAtQAQN/QZQREBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaIABB1hBBnxEQEiEBQagRIAAQEEHCERAQIAFByBAQEBAQIQJBzBEgAhAWDxpBAAtSAQN/QeQREBchACAAQdYQEBFBAEYEf0EADxpBAAVBAAsaQe4RIAAQGCEBIAEQDyECIAJBAEYEf0H1ERAHGkEADxpBAAVBlBIQBRogAg8aQQALCw4AQakSEAcaQQAPGkEAC2ABBX9BgBAQAiEAIABB4BJB8hIQBCEBQYYTIAEQECECIAIQDyEDIANBAEcEf0GQExAGGkEBDxpBAAVBAAsaQdITQeUTIAFBrRBBABATIQQgBBAUGkGFFBAHGkEADxpBAAtrAQF/QaEUEA4aIABBxRRBABABEAsaIABB2xRBARABEAsaIABB8BRBAhABEAsaIABBiBVBAxABEAsaIABBnxVBBBABEAsaQYAQEAIhASABQboVQQEQA0EARwR/EB4aQQAFQQALGkEADxpBAAsIAEEADxpBAAsLnAcjAEGAEAsQDAAAAGFmZmluZXNjcmlwdABBkBALHRkAAABObyBBZmZpbmVTY3JpcHQgZmlsZSBvcGVuAEGtEAsEAAAAAABBsRALEQ0AAABhZmZpbmVzY3JpcHQgAEHCEAsGAgAAACAiAEHIEAsFAQAAACIAQc0QCwkFAAAAY2hlY2sAQdYQCwsHAAAALmFmZmluZQBB4RALFhIAAABBZmZpbmVTY3JpcHQgQ2hlY2sAQfcQCwgEAAAAZXZhbABB/xALFREAAABBZmZpbmVTY3JpcHQgRXZhbABBlBELCwcAAABjb21waWxlAEGfEQsJBQAAAC53YXNtAEGoEQsaFgAAAGFmZmluZXNjcmlwdCBjb21waWxlICIAQcIRCwoGAAAAIiAtbyAiAEHMEQsYFAAAAEFmZmluZVNjcmlwdCBDb21waWxlAEHkEQsKBgAAAGZvcm1hdABB7hELBwMAAABmbXQAQfURCx8bAAAARmlsZSBmb3JtYXR0ZWQgc3VjY2Vzc2Z1bGx5AEGUEgsVEQAAAEZvcm1hdHRpbmcgZmFpbGVkAEGpEgs3MwAAAEFmZmluZVNjcmlwdCBMU1Agc3RvcHBlZCAocmVsb2FkIHdpbmRvdyB0byByZXN0YXJ0KQBB4BILEg4AAABsc3Auc2VydmVyUGF0aABB8hILFBAAAABhZmZpbmVzY3JpcHQtbHNwAEGGEwsKBgAAAHdoaWNoIABBkBMLQj4AAABBZmZpbmVTY3JpcHQgTFNQIHNlcnZlciBub3QgZm91bmQuIExhbmd1YWdlIGZlYXR1cmVzIGRpc2FibGVkLgBB0hMLEw8AAABhZmZpbmVzY3JpcHRMc3AAQeUTCyAcAAAAQWZmaW5lU2NyaXB0IExhbmd1YWdlIFNlcnZlcgBBhRQLHBgAAABBZmZpbmVTY3JpcHQgTFNQIHN0YXJ0ZWQAQaEUCyQgAAAAQWZmaW5lU2NyaXB0IGV4dGVuc2lvbiBhY3RpdmF0ZWQAQcUUCxYSAAAAYWZmaW5lc2NyaXB0LmNoZWNrAEHbFAsVEQAAAGFmZmluZXNjcmlwdC5ldmFsAEHwFAsYFAAAAGFmZmluZXNjcmlwdC5jb21waWxlAEGIFQsXEwAAAGFmZmluZXNjcmlwdC5mb3JtYXQAQZ8VCxsXAAAAYWZmaW5lc2NyaXB0LnJlc3RhcnRMc3AAQboVCw8LAAAAbHNwLmVuYWJsZWQAYxZhZmZpbmVzY3JpcHQub3duZXJzaGlwCwAAABYAAAACAAAAFwAAAAEAABgAAAACAAAAGQAAAAAAGgAAAAAAGwAAAAAAHAAAAAAAHQAAAAAAHgAAAAAAHwAAAAEAACAAAAAAAA==";
const _wasmBytes = Buffer.from(_wasmBase64, "base64");

// Per-process opaque-handle table for host objects (ExtensionContext,
// Terminal, LanguageClient, ...). Wasm refers to host objects by integer id.
const _handles = new Map();
let _nextHandle = 1;
function _registerHandle(obj) {
const h = _nextHandle++;
_handles.set(h, obj);
return h;
}
function _getHandle(h) { return _handles.get(h); }
function _freeHandle(h) { _handles.delete(h); }

// WASI-style minimal imports — affinescript codegen wires in fd_write on
// every module so we satisfy that even if no IO is exercised.
function _writeFdString(fd, ptr, len, memory) {
const bytes = new Uint8Array(memory.buffer, ptr, len);
const text = new TextDecoder("utf-8").decode(bytes);
if (fd === 1) process.stdout.write(text);
else if (fd === 2) process.stderr.write(text);
}

let _instance = null;
let _memory = null;

function _buildImports() {
const wasi_snapshot_preview1 = {
fd_write: (fd, iovs_ptr, iovs_len, nwritten_ptr) => {
// Minimal fd_write: walk the iovec array and concat to fd.
const view = new DataView(_memory.buffer);
let total = 0;
for (let i = 0; i < iovs_len; i++) {
const ptr = view.getUint32(iovs_ptr + i * 8, true);
const len = view.getUint32(iovs_ptr + i * 8 + 4, true);
_writeFdString(fd, ptr, len, _memory);
total += len;
}
view.setUint32(nwritten_ptr, total, true);
return 0;
},
};
// Phase 2 hook: callers can replace exports.extraImports with a function
// returning a `{ ModuleName: { exportName: fn, ... } }` map of concrete
// host bindings (e.g. the @hyperpolymath/affine-vscode adapter). Default
// is empty so the shim works standalone.
const extras = (typeof exports.extraImports === "function")
? exports.extraImports()
: {};
return { wasi_snapshot_preview1, ...extras };
}

async function _init() {
if (_instance) return _instance;
const { instance } = await WebAssembly.instantiate(_wasmBytes, _buildImports());
_instance = instance;
_memory = instance.exports.memory;
// Phase 2 hook: the active instance + memory must be reachable from
// bindings adapters that need to read string args / call back into the
// wasm table. Surface them on the exports object as soon as init finishes.
exports._instance = instance;
exports._memory = instance.exports.memory;
return _instance;
}

exports.activate = async function activate(context) {
const inst = await _init();
if (typeof inst.exports.activate === "function") {
const ctxHandle = _registerHandle(context);
return inst.exports.activate(ctxHandle);
}
};

exports.deactivate = async function deactivate() {
if (!_instance) return;
if (typeof _instance.exports.deactivate === "function") {
return _instance.exports.deactivate();
}
};

// Surfaced for Phase 2 binding modules to register concrete vscode-API
// implementations before the Wasm is instantiated.
exports._registerHandle = _registerHandle;
exports._getHandle = _getHandle;
exports._freeHandle = _freeHandle;
9 changes: 4 additions & 5 deletions editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"activationEvents": [
"onLanguage:affinescript"
],
"main": "./out/extension.js",
"main": "./out/extension.cjs",
"contributes": {
"languages": [
{
Expand Down Expand Up @@ -135,15 +135,14 @@
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"compile": "affinescript compile src/extension.affine -o out/extension.cjs",
"watch": "echo 'watch mode not implemented for AffineScript source — re-run npm run compile'",
"guard": "../../tools/check-no-extension-ts.sh",
"package": "vsce package",
"publish": "vsce publish"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/vscode": "^1.80.0",
"typescript": "^5.0.0",
"vsce": "^2.15.0"
},
"dependencies": {
Expand Down
Loading
Loading