import express, { Request, Response } from 'express'; import cors from 'cors'; import { exec } from 'child_process'; import { promises as fs } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { promisify } from 'util'; const execAsync = promisify(exec); const app = express(); const PORT = process.env.PORT || 3456; const EMSDK_PATH = process.env.EMSDK_PATH || `${process.env.HOME}/wasm-toolchains/emsdk`; const SWIFTWASM_PATH = process.env.SWIFTWASM_PATH || `${process.env.HOME}/wasm-toolchains/swiftwasm`; app.use(cors()); app.use(express.json({ limit: '5mb' })); app.use(express.static('public')); interface CompileRequest { code: string; flags?: string; } interface Diagnostic { line?: number; column?: number; message: string; severity: 'error' | 'warning' | 'note'; file?: string; } function getEmsdkEnvCommand(): string { return `source "${EMSDK_PATH}/emsdk_env.sh"`; } function getSwiftPath(): string { return `${SWIFTWASM_PATH}/usr/bin`; } async function findCompiler(compilerName: string, extraPaths: string[] = []): Promise { const paths = [...extraPaths, ...(process.env.PATH?.split(':') || [])]; for (const p of paths) { try { await fs.access(join(p, compilerName)); return join(p, compilerName); } catch { /* ignore */ } } return null; } async function findInPathsOnly(compilerName: string, searchPaths: string[]): Promise { for (const p of searchPaths) { try { await fs.access(join(p, compilerName)); return join(p, compilerName); } catch { /* ignore */ } } return null; } async function canTargetWasm32(swiftcPath: string): Promise { try { const testDir = await fs.mkdtemp(join(tmpdir(), 'swift-wasm-test-')); const testFile = join(testDir, 'test.swift'); await fs.writeFile(testFile, 'print("hello")\n'); await execAsync(`"${swiftcPath}" -target wasm32-unknown-wasi -o "${join(testDir, 'test.wasm')}" "${testFile}"`, { timeout: 15000 }); await fs.rm(testDir, { recursive: true, force: true }).catch(() => { }); return true; } catch { return false; } } function parseDiagnostics(stderr: string, lang: 'cpp' | 'swift'): Diagnostic[] { const diagnostics: Diagnostic[] = []; if (!stderr) return diagnostics; const lines = stderr.split('\n'); if (lang === 'cpp') { // emcc/clang: file.cpp:12:34: error: message const re = /^(.+?):(\d+):(\d+):\s*(error|warning|note):\s*(.+)$/; for (const line of lines) { const m = line.match(re); if (m) { diagnostics.push({ file: m[1], line: parseInt(m[2], 10), column: parseInt(m[3], 10), severity: m[4] as Diagnostic['severity'], message: m[5], }); } } } else { // swiftc: file.swift:12:34: error: message const re = /^(.+?):(\d+):(\d+):\s*(error|warning|note):\s*(.+)$/; for (const line of lines) { const m = line.match(re); if (m) { diagnostics.push({ file: m[1], line: parseInt(m[2], 10), column: parseInt(m[3], 10), severity: m[4] as Diagnostic['severity'], message: m[5], }); } } } return diagnostics; } function parseWasmExportsImports(wasmBytes: Buffer): { exports: any[]; imports: any[]; memories: any[] } { const result = { exports: [] as any[], imports: [] as any[], memories: [] as any[] }; let pos = 8; // skip magic + version while (pos < wasmBytes.length) { const id = wasmBytes[pos++]; if (id > 11) break; let len = 0; let shift = 0; while (true) { const b = wasmBytes[pos++]; len |= (b & 0x7f) << shift; shift += 7; if ((b & 0x80) === 0) break; } const end = pos + len; if (id === 1) { // Type section - skip } else if (id === 2) { // Import section let count = readLeb(wasmBytes, pos); for (let i = 0; i < count.value; i++) { const mod = readString(wasmBytes, pos + count.bytes); const name = readString(wasmBytes, pos + count.bytes + mod.bytes); const kind = wasmBytes[pos + count.bytes + mod.bytes + name.bytes]; result.imports.push({ module: mod.str, name: name.str, kind }); } } else if (id === 7) { // Export section let count = readLeb(wasmBytes, pos); for (let i = 0; i < count.value; i++) { const name = readString(wasmBytes, pos + count.bytes); const kind = wasmBytes[pos + count.bytes + name.bytes]; const idx = readLeb(wasmBytes, pos + count.bytes + name.bytes + 1); result.exports.push({ name: name.str, kind, index: idx.value }); } } else if (id === 5) { // Memory section let count = readLeb(wasmBytes, pos); for (let i = 0; i < count.value; i++) { const flags = wasmBytes[pos + count.bytes]; const min = readLeb(wasmBytes, pos + count.bytes + 1); result.memories.push({ flags, min: min.value }); } } pos = end; } return result; } function readLeb(buf: Buffer, offset: number): { value: number; bytes: number } { let value = 0, shift = 0, bytes = 0; while (true) { const b = buf[offset + bytes++]; value |= (b & 0x7f) << shift; shift += 7; if ((b & 0x80) === 0) break; } return { value, bytes }; } function readString(buf: Buffer, offset: number): { str: string; bytes: number } { const len = readLeb(buf, offset); const str = buf.slice(offset + len.bytes, offset + len.bytes + len.value).toString('utf8'); return { str, bytes: len.bytes + len.value }; } // --- EXAMPLES --- const examples = { cpp: [ { name: 'Hello WASM', code: `#include #include int main() { printf("Hello from C++ WASM!\\n"); return 0; } ` }, { name: 'Exported Functions', code: `#include extern "C" { EMSCRIPTEN_KEEPALIVE int add(int a, int b) { return a + b; } EMSCRIPTEN_KEEPALIVE int factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); } } int main() { return 0; } ` }, { name: 'Memory & Pointer', code: `#include #include extern "C" { EMSCRIPTEN_KEEPALIVE int sumArray(int* arr, int len) { int sum = 0; for (int i = 0; i < len; i++) sum += arr[i]; return sum; } EMSCRIPTEN_KEEPALIVE void reverseInPlace(int* arr, int len) { for (int i = 0; i < len / 2; i++) { int tmp = arr[i]; arr[i] = arr[len - 1 - i]; arr[len - 1 - i] = tmp; } } } int main() { return 0; } ` }, { name: 'Fibonacci Benchmark', code: `#include #include extern "C" { EMSCRIPTEN_KEEPALIVE int fib(int n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); } } int main() { for (int i = 0; i <= 20; i++) { printf("fib(%d) = %d\\n", i, fib(i)); } return 0; } ` } ], swift: [ { name: 'Hello WASM', code: `print("Hello from Swift WASM!") ` }, { name: 'Exported Functions', code: `@_cdecl("add") public func add(a: Int32, b: Int32) -> Int32 { return a + b } @_cdecl("multiply") public func multiply(a: Int32, b: Int32) -> Int32 { return a * b } ` }, { name: 'Recursive Factorial', code: `@_cdecl("factorial") public func factorial(n: Int32) -> Int32 { if n <= 1 { return 1 } return n * factorial(n: n - 1) } ` }, { name: 'Array Sum', code: `@_cdecl("sumArray") public func sumArray(ptr: UnsafePointer, count: Int32) -> Int32 { var sum: Int32 = 0 for i in 0.. { res.json(examples); }); app.get('/api/status', async (_req: Request, res: Response) => { const emcc = await findCompiler('emcc', [`${EMSDK_PATH}/upstream/emscripten`]); const swiftc = await findCompiler('swiftc', [getSwiftPath()]); const swiftWasmCapable = swiftc ? await canTargetWasm32(swiftc) : false; res.json({ emccAvailable: !!emcc, emccPath: emcc || null, swiftcAvailable: swiftWasmCapable, swiftcPath: swiftWasmCapable ? swiftc : null, emsdkPath: EMSDK_PATH, swiftwasmPath: SWIFTWASM_PATH }); }); app.post('/api/compile/cpp', async (req: Request, res: Response) => { const { code, flags } = req.body as CompileRequest; if (!code || typeof code !== 'string') { res.status(400).json({ success: false, error: 'Missing code field' }); return; } const workDir = await fs.mkdtemp(join(tmpdir(), 'cpp-wasm-')); try { const emcc = await findInPathsOnly('emcc', [`${EMSDK_PATH}/upstream/emscripten`]); if (!emcc) { res.status(503).json({ success: false, error: 'emcc not found. Run: ./scripts/install-toolchains.sh' }); return; } await fs.writeFile(join(workDir, 'main.cpp'), code); const extraFlags = flags ? ` ${flags}` : ''; const cmd = `bash -c '${getEmsdkEnvCommand()} && emcc "${workDir}/main.cpp" -o "${workDir}/main.js" -s EXPORTED_FUNCTIONS="['_main']" -s EXPORTED_RUNTIME_METHODS="['ccall','cwrap','UTF8ToString']" -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=16MB -s STACK_SIZE=1MB -s SINGLE_FILE=1 -O2${extraFlags}'`; const { stdout, stderr } = await execAsync(cmd, { timeout: 60000, env: { ...process.env, HOME: process.env.HOME || '' } }); const jsFile = await fs.readFile(join(workDir, 'main.js'), 'utf-8'); const diagnostics = parseDiagnostics(stderr, 'cpp'); res.json({ success: true, js: jsFile, stdout, stderr, diagnostics }); } catch (err: any) { const diagnostics = parseDiagnostics(err.stderr || '', 'cpp'); res.json({ success: false, error: err.stderr || err.message, stdout: err.stdout || '', diagnostics }); } finally { await fs.rm(workDir, { recursive: true, force: true }).catch(() => { }); } }); app.post('/api/compile/swift', async (req: Request, res: Response) => { const { code, flags } = req.body as CompileRequest; if (!code || typeof code !== 'string') { res.status(400).json({ success: false, error: 'Missing code field' }); return; } const workDir = await fs.mkdtemp(join(tmpdir(), 'swift-wasm-')); try { const swiftc = await findInPathsOnly('swiftc', [getSwiftPath()]); if (!swiftc) { res.status(503).json({ success: false, error: 'swiftc (SwiftWasm) not found. Run: ./scripts/install-toolchains.sh' }); return; } await fs.writeFile(join(workDir, 'main.swift'), code); const extraFlags = flags ? ` ${flags}` : ''; const cmd = `PATH="${getSwiftPath()}:$PATH" swiftc -target wasm32-unknown-wasi -o "${workDir}/main.wasm" "${workDir}/main.swift"${extraFlags}`; const { stdout, stderr } = await execAsync(cmd, { timeout: 60000, env: { ...process.env, HOME: process.env.HOME || '' } }); const wasmBytes = await fs.readFile(join(workDir, 'main.wasm')); const wasmInfo = parseWasmExportsImports(wasmBytes); const diagnostics = parseDiagnostics(stderr, 'swift'); res.json({ success: true, wasm: wasmBytes.toString('base64'), stdout, stderr, diagnostics, wasmInfo }); } catch (err: any) { const diagnostics = parseDiagnostics(err.stderr || '', 'swift'); res.json({ success: false, error: err.stderr || err.message, stdout: err.stdout || '', diagnostics }); } finally { await fs.rm(workDir, { recursive: true, force: true }).catch(() => { }); } }); app.post('/api/inspect', async (req: Request, res: Response) => { const { wasmBase64 } = req.body; if (!wasmBase64 || typeof wasmBase64 !== 'string') { res.status(400).json({ success: false, error: 'Missing wasmBase64 field' }); return; } try { const wasmBytes = Buffer.from(wasmBase64, 'base64'); const info = parseWasmExportsImports(wasmBytes); res.json({ success: true, info }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } }); app.listen(PORT, () => { console.log(`WASM Compiler Bridge running at http://localhost:${PORT}`); console.log(`EMSDK_PATH: ${EMSDK_PATH}`); console.log(`SWIFTWASM_PATH: ${SWIFTWASM_PATH}`); });