From 8f7711305fa54e8e0c220d621eb75eacc7c37684 Mon Sep 17 00:00:00 2001 From: cs01 Date: Fri, 27 Feb 2026 20:27:47 -0800 Subject: [PATCH 1/3] update rules --- .llms/rules/rules.md | 1 - CLAUDE.md | 293 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 1 deletion(-) delete mode 120000 .llms/rules/rules.md create mode 100644 CLAUDE.md diff --git a/.llms/rules/rules.md b/.llms/rules/rules.md deleted file mode 120000 index 4726d29c..00000000 --- a/.llms/rules/rules.md +++ /dev/null @@ -1 +0,0 @@ -../../.claude/rules.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..7ffee29a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,293 @@ +# ChadScript Rules + +## Testing & Commit Workflow + +After completing each todo: + +1. Run unit tests +2. If tests pass, commit the changes +3. If tests fail, fix them before moving to the next todo +4. Never move on to the next todo while tests are failing + +## Self-Hosting Verification + +Before considering any feature complete, run the full self-hosting chain: + +1. `npm run verify` — runs tests and self-hosting in parallel (preferred) +2. `npm run verify:quick` — same but skips Stage 2 (day-to-day dev) + +Or manually: + +1. `npm test` — all tests pass (auto-uses native compiler if `.build/chad` exists) +2. `bash scripts/self-hosting.sh` — full 3-stage self-hosting +3. `bash scripts/self-hosting.sh --quick` — skip Stage 2 + +New features have complex side effects that may not be caught by unit tests alone. A change that passes all tests can still break self-hosting. The Stage 2 test is the true verification — it proves the compiler's output is correct enough to compile itself. + +## Stale Native Compiler + +After a rebase or merge that brings in new codegen features, `.build/chad` becomes stale — it was compiled +from the old source and doesn't know how to compile the new features. **Rebuild it**: + +```bash +rm -f .build/chad && node dist/chad-node.js build src/chad-native.ts -o .build/chad +``` + +Tests auto-detect `.build/chad` and use it over `node dist/chad-node.js`. A stale native compiler causes +mysterious test failures that pass fine with the node compiler. + +## Worktree Setup + +When working in a git worktree, `vendor/` and `c_bridges/*.o` must exist. Symlinks to the main repo work: + +```bash +ln -s /path/to/main/repo/vendor vendor +``` + +The `c_bridges/*.c` source files are tracked in git, but the `.o` files are built by `scripts/build-vendor.sh`. +If `.o` files are missing, either run the build script or symlink from a repo that has them built. + +# ChadScript Architecture Guide + +## What It Is + +TypeScript-to-native compiler using LLVM IR. Compiles .ts/.js files to native binaries via: Parser → AST → Semantic Analysis → LLVM IR Codegen → llc (assembler) → clang (linker) → native binary. + +## Key Directories + +| Dir | Purpose | +| ----------------------------------------- | --------------------------------------------------------------------------------- | +| `src/semantic/` | Semantic analysis passes run before codegen (closure mutation, union types) | +| `src/codegen/` | LLVM IR code generation (the core) | +| `src/codegen/expressions/method-calls.ts` | Central dispatcher for all `object.method()` calls | +| `src/codegen/types/collections/string/` | String method IR generators (manipulation.ts, search.ts, split.ts, etc.) | +| `src/codegen/types/collections/string.ts` | `StringGenerator` facade that delegates to sub-modules | +| `src/codegen/types/collections/array.ts` | `ArrayGenerator` facade that delegates to sub-modules | +| `src/codegen/types/collections/array/` | Array sub-modules (literal, mutators, search-predicate, iteration, combine, etc.) | +| `src/codegen/stdlib/` | Built-in module generators (console.ts, process.ts, fs.ts, math.ts, etc.) | +| `src/codegen/infrastructure/` | Core: generator-context.ts, symbol-table.ts, type-resolver.ts | +| `src/codegen/llvm-generator.ts` | Main orchestrator, delegates to sub-generators | +| `src/ast/types.ts` | AST node type definitions | +| `tests/compiler.test.ts` | Main test suite | +| `tests/test-discovery.ts` | Auto-discovers test fixtures via `@test` annotations | +| `tests/fixtures/` | Test fixture programs organized by category (auto-discovered) | +| `c_bridges/` | C bridge files for complex runtime helpers (regex, json, os, etc.) | + +## How to Add a New String Method + +1. **IR Generation**: Add function in `src/codegen/types/collections/string/manipulation.ts` (or search.ts, etc.) +2. **Facade**: Add `doGenerateX()` in `src/codegen/types/collections/string.ts` (StringGenerator class) +3. **Dispatch**: Add `if (method === 'x')` block in `src/codegen/expressions/method-calls.ts` (~line 812 area) +4. **Handler**: Add `private handleX()` method in method-calls.ts +5. **Context**: If consumers access via a sub-generator context interface, ensure `readonly stringGen: IStringGenerator` is declared +6. **Test**: Add fixture in `tests/fixtures/strings/` (auto-discovered, no registry needed) + +**NOTE**: Prefer direct field access (`ctx.stringGen.doMethod()`) over adding wrapper methods to `IGeneratorContext`. Concrete type propagation in `loadFieldValue` (member.ts) ensures chained access through interface fields works in the native compiler. + +## How to Add a New Built-in (process.x, console.x, etc.) + +1. Check if existing generator handles it (e.g., `src/codegen/stdlib/process.ts`) +2. Most built-ins are handled inline in `method-calls.ts` for performance +3. For member access (not method calls), look at `src/codegen/expressions/member.ts` +4. Test: add fixture in `tests/fixtures/builtins/` (auto-discovered, no registry needed) + +## Struct Types + +| LLVM Type | JS Type | +| ---------------------------------------- | ----------------------------------------- | +| `%Array = type { double*, i32, i32 }` | `number[]` (data ptr, length, capacity) | +| `%StringArray = type { i8**, i32, i32 }` | `string[]` | +| `%ObjectArray = type { i8*, i32, i32 }` | `object[]` | +| `%Uint8Array = type { i8*, i32, i32 }` | `Uint8Array` (data ptr, length, capacity) | +| `i8*` | `string` (null-terminated C string) | +| `double` | `number` | +| `i1` | `boolean` | + +## Test Patterns + +Tests are auto-discovered from `tests/fixtures/` via `@test` annotations in `tests/test-discovery.ts`. +No manual registry — just add a fixture file and it's picked up automatically. + +Annotation format (in the first 10 lines of each fixture file): + +- `// @test-exit-code: 12` — assert process exits with code 12 +- `// @test-args: hello world` — pass CLI args to the compiled binary +- `// @test-description: ...` — custom test description +- `// @test-skip` — exclude from auto-discovery + +Defaults (no annotation needed): + +- `expectTestPassed: true` — asserts stdout contains `TEST_PASSED` and exit code 0 +- Description auto-generated from filename: `string-split-length.ts` → `string split length` + +Run tests: `npm test` or `npm run test:full` (via `node scripts/test.js`) +Run tests + self-hosting: `npm run verify` (or `npm run verify:quick` to skip Stage 2) +Build: `npm run build` (TypeScript → dist/) + +Tests auto-detect `.build/chad` and use it instead of `node dist/chad-node.js` (~10x faster per compile). +`compiler.test.ts` runs at concurrency 32; `smoke.test.ts` at concurrency 8. + +## Useful Patterns + +- `ctx.nextTemp()` — get next SSA temp variable name (%1, %2, etc.) +- `ctx.nextLabel(prefix)` — get next unique label for control flow +- `ctx.emit(line)` — emit a line of LLVM IR +- `ctx.generateExpression(expr, params)` — recursively generate an expression +- `ctx.setVariableType(name, type)` — tell the type system what type a temp is +- `createStringConstant(ctx, value)` — create a global string constant, returns i8\* +- `GC_malloc_atomic(size)` — allocate GC'd memory for non-pointer data (strings) +- `GC_malloc(size)` — allocate GC'd memory that may contain pointers + +## Structured IR Builders + +Prefer structured builder methods over raw `ctx.emit()` for these instructions: + +- `ctx.emitStore(type, value, ptr)` — instead of `ctx.emit(\`store ${type} ${value}, ${type}\* ${ptr}\`)` +- `ctx.emitLoad(type, ptr)` — instead of `ctx.emit(\`${tmp} = load ${type}, ${type}\* ${ptr}\`)` +- `ctx.emitGep(baseType, ptr, indices)` — instead of `ctx.emit(\`${tmp} = getelementptr ...\`)` +- `ctx.emitCall(retType, func, args)` — instead of `ctx.emit(\`${tmp} = call ...\`)` +- `ctx.emitCallVoid(func, args)` — instead of `ctx.emit(\`call void ...\`)` +- `ctx.emitBitcast(value, fromType, toType)` — instead of `ctx.emit(\`${tmp} = bitcast ...\`)` +- `ctx.emitIcmp(pred, type, lhs, rhs)` — instead of `ctx.emit(\`${tmp} = icmp ...\`)` +- `ctx.emitBr(label)` / `ctx.emitBrCond(cond, then, else)` / `ctx.emitLabel(name)` — control flow + +Keep `ctx.emit()` for instructions without builders: `phi`, `select`, `add`, `sub`, `mul`, `zext`, +`sitofp`, `fptosi`, `fcmp`, `alloca`, `ptrtoint`, `inttoptr`, `call void @llvm.memcpy...`. +Also keep `emit()` when you need `inbounds` on GEP or `!tbaa` metadata on loads/stores (builders +don't support these qualifiers yet). + +## Terminator Classification + +LLVM basic blocks must end with exactly one terminator instruction (`ret`, `br`, `unreachable`, `switch`). +Rather than parsing emitted strings to detect terminators, we use a parallel `outputIsTerminator: boolean[]` +that auto-classifies every instruction at `emit()` time. Use `ctx.lastInstructionIsTerminator()` to check. + +**Three-way sync requirement**: The classification logic (`classifyTerminator`) exists in three places +that must stay identical: `BaseGenerator` (protected), `MockGeneratorContext` (private), and +`LLVMGenerator` inherits from `BaseGenerator`. If you add a new terminator (e.g., `invoke`, `indirectbr`), +update all three. + +Builder methods (`emitRet`, `emitRetVoid`, `emitBr`, `emitBrCond`, `emitUnreachable`, `emitLabel`) are +available on `BaseGenerator`, `LLVMGenerator`, and `MockGeneratorContext` for type-safe terminator emission. + +## Method Dispatch Flow + +`method-calls.ts` → `generateMethodCall()` checks object type and method name: + +1. Static methods first (Object.keys, Array.from, Promise.all, etc.) +2. Built-in objects (console, process, fs, path, JSON, Math, Date) +3. String methods (trim, indexOf, split, replace, etc.) +4. Array methods (push, pop, map, filter, find, etc.) +5. Map/Set methods +6. Class/interface method dispatch (vtable lookup) + +## C Bridges + +For complex runtime logic (nested loops, string manipulation, data structure building), prefer writing +C bridge functions in `c_bridges/` over raw LLVM IR string concatenation. C bridges are easier to read, +debug, and maintain. + +Pattern: + +1. Create `c_bridges/your-bridge.c` with `cs_` prefixed functions +2. Add build step in `scripts/build-vendor.sh` (compile to `.o`) +3. Declare extern functions in LLVM IR and call them from codegen +4. Add conditional linking in `src/compiler.ts` and `src/native-compiler-lib.ts` + +Existing bridges: `regex-bridge.c`, `yyjson-bridge.c`, `os-bridge.c`, `child-process-bridge.c`, +`child-process-spawn.c`, `lws-bridge.c`, `treesitter-bridge.c`. + +## Codegen Quick Rules + +1. **Hoist allocas to entry block** — never in conditional branches +2. **Store pointers as `i8*`** — `double` loses 64-bit precision +3. **Check class before interface** — try `findClassImplementingInterface()` BEFORE `interfaceStructGen.hasInterface()` +4. **Load array values in objects** — load the value, don't pass the alloca +5. **Type cast field order must match FULL struct layout** — when the type extends a parent interface, the struct includes ALL parent fields. `as { name, closureInfo }` on a `LiftedFunction extends FunctionNode` (10 fields) reads index 1 instead of index 9. Include every field. +6. **`ret void` not `unreachable`** at end of void functions +7. **Class structs: boolean is `i1`; Interface structs: boolean is `double`** +8. **Propagate declared type before generating RHS for collection fields** — when a class field is typed `Set`, `Map`, etc., call `setCurrentDeclaredSetType` / `setCurrentDeclaredMapType` before `generateExpression` on the RHS so that `new Set()` / `new Map()` without explicit type args picks the right generator. See `handleClassFieldAssignment` in `assignment-generator.ts`. +9. **Set feature flags when emitting gated extern calls** — runtime declarations for C bridges (yyjson, curl, etc.) are conditionally emitted behind flags like `usesJson`, `usesCurl`. Any code path that emits `call @csyyjson_*` must call `ctx.setUsesJson(true)`, etc. Missing this causes "undefined value" errors from `opt` because the `declare` is never emitted. + +## Interface Field Iteration + +When building field lists for an interface (keys/types arrays for ObjectMetadata), always use +`getAllInterfaceFields(interfaceDef)` instead of `interfaceDef.fields`. The latter only returns the +interface's OWN fields, missing inherited fields from `extends`. This causes wrong GEP indices for +any interface with inheritance. `allocateDeclaredInterface` does this correctly; several other methods +(`allocateMemberAccessInterface`, `allocateFunctionInterfaceReturn`, etc.) currently do not. + +## Code Style + +- Prettier auto-formats code; run `npm run format` to fix, `npm run format:check` to verify +- One-line comments are helpful on dense codegen blocks — explain the "why" or the LLVM IR pattern, not the "what" +- Use named AST types from `src/ast/types.ts` for type assertions instead of inline `as { ... }` structs +- There are several MASSIVE files. Where possible, do not add to them. Make a new file, and leave a comment in the other file to not touch it anymore, and to progressively break it down into smaller files. + +## Patterns That Crash Native Code + +1. **`new` in class field initializers** — codegen handles simple `new X()` in field initializers (both explicit and default constructors), but complex nested class instantiation may have edge cases. Prefer initializing in constructors for safety. +2. **Type assertions must match real struct field order AND count** — `as { type, left, right }` on a struct that's `{ type, op, left, right }` causes GEP to read wrong fields. Fields must be a PREFIX of the real struct in EXACT order. **Watch out for `extends`**: if `Child extends Parent`, the struct has ALL of Parent's fields first, then Child's. A type assertion on a Child must include Parent's fields too — even optional ones the object literal doesn't set (the compiler allocates slots for them anyway, filled with null/0). +3. **Never insert new optional fields in the MIDDLE of an interface** — The native compiler determines struct layouts from object literal creation sites. If an interface has multiple creation sites (e.g., `MethodCallNode` is created in parser-ts, parser-native, and codegen), inserting a new field before existing ones shifts GEP indices and breaks creation sites that don't include the new field. **Always add new optional fields at the END of interfaces.** Root cause: the native compiler doesn't unify struct layouts from interface definitions — it uses object literal field order, and different creation sites may have different subsets of fields. +4. **`alloca` for collection structs stored in class fields** — `%Set`, `%StringSet`, `%Map`, and similar structs must be heap-allocated via `GC_malloc`, not `alloca`. Stack-allocated structs become dangling pointers when stored in a class field after the constructor returns. Use `emitCall("i8*", "@GC_malloc", "i64 N") + emitBitcast(...)` instead of `emit("... = alloca %Foo")`. +5. **`||` fallback makes member access opaque** — `const x = foo.bar || { field: [] }` stores the result as `i8*` (opaque pointer) because the `||` merges two different types. Subsequent `.field` access on `x` does NOT generate a GEP — it just returns `x` itself. Fix: use a ternary that preserves the typed path: `const y = foo.bar ? foo.bar.field : []`. This applies to any `||` or `??` where the fallback is an inline object literal. + +## Stage 0 Compatibility + +Array-of-objects field access (`props[i].name`) works correctly — `argparse.ts` uses it extensively +(`this.args[i].name`, `this.parsedFlags[i].value`). Previous crashes attributed to this pattern +were actually caused by type assertions with wrong field counts (see Patterns That Crash Native Code #2). + +Self-hosting limitations: + +- **No import aliases** — `import { foo as bar }` compiles `bar(...)` as `@_cs_bar` which doesn't match the original `@_cs_foo`. Use the original name. +- **No union type parameters in standalone functions** — `fn(x: Expression)` where `Expression` is a union emits the TS type name literally. Keep union-typed parameters in class methods. + +## Module System + +ChadScript merges all imported files into one flat AST. Imports trigger file resolution and AST merging. + +- **`export default`**: Parser stores the exported name in `ast.defaultExportName`. At import resolution time (`compiler.ts` `compileMultiFile`), the default import's local name is mapped to the exported name via `importAliases`. Codegen resolves aliases through `resolveImportAlias()`. +- **Re-exports** (`export { foo } from './bar'`): The parser synthesizes `ImportDeclaration` entries for each re-exported name. Since ChadScript merges all files, re-exports are semantically equivalent to imports — they just trigger file resolution and AST merging. + +## String Enums + +Parser preserves string values in `EnumMember.stringValue` and marks `EnumDeclaration.isString = true`. Codegen handles string enums in a separate `handleStringEnumMemberAccess` method in `member.ts` (not in `handleEnumMemberAccess` — adding locals there causes native compiler issues). Type inference in `resolveMemberAccessType` detects enum member access and returns `stringType` for string enums. Type assertions on `EnumMember` must include all fields in order: `{ name, value, stringValue }`. + +## Optional Method Calls (`?.()`) + +`MethodCallNode.optional` (must be at END of interface — see rule #3 above) triggers null-check branching in `generateOptionalMethodCall` in `method-calls.ts`. Pattern: evaluate object → icmp null → branch → phi merge, similar to `generateOptionalChain` for property access. + +## Async/Await Type Tracking + +`allocateAwaitResult` in `variable-allocator.ts` must inspect the awaited expression to determine the correct SymbolKind. Default is `i8*`/string, but `Promise.all()` resolves to `%ObjectArray*`. For each new async API that resolves to a specific type, add a detection case to `allocateAwaitResult`. + +## Semantic Analysis Passes + +Semantic passes live in `src/semantic/` and run before codegen (called from `LLVMGenerator.generateParts()`). +They catch errors that would produce silently wrong native code — the native compiler can't throw exceptions +at runtime, so these must be compile-time errors. + +Current passes: + +- **`closure-mutation-checker.ts`** — ChadScript closures capture by value. Mutating a variable after capture + produces silently wrong results. This pass detects post-capture assignments and emits a compile error. +- **`union-type-checker.ts`** — Type alias unions like `type Mixed = string | number` bypass the inline union + check. This pass resolves aliases and rejects unions whose members map to different LLVM representations. + +To add a new semantic pass: create `src/semantic/your-check.ts`, export a `checkX(ast: AST): void` function, +and call it from `generateParts()` in `llvm-generator.ts`. + +## LLVMGenerator.reset() + +`LLVMGenerator.reset()` calls `super.reset()` to reset all `BaseGenerator` fields, then clears its own +additional fields. If you add new per-function state to either class, add the reset in the right place: +base fields in `BaseGenerator.reset()`, LLVMGenerator-only fields in the override after `super.reset()`. + +## Expression Orchestrator — No Silent Nulls + +`orchestrator.ts` must **never** silently generate null pointers (`inttoptr i64 0 to i8*`) for unrecognized +expressions. These nulls are UB that LLVM `-O2` can exploit to prune unrelated code paths. Both fallback +paths (empty type, unsupported type) now call `ctx.emitError()` which is `never`-typed — it exits the +compiler immediately. If a new expression type is added to the parser, add a handler in the orchestrator; +don't rely on a fallback. From def43fc317b44d2a8879ebd302ba6ed5b1da602f Mon Sep 17 00:00:00 2001 From: cs01 Date: Fri, 27 Feb 2026 20:35:36 -0800 Subject: [PATCH 2/3] add connId to wsevent + wssend built-in, rename usesmongoose->useshttpserver, delete mongoose.ts --- c_bridges/lws-bridge.c | 30 +- c_bridges/lws-bridge.h | 1 + docs/stdlib/http-server.md | 18 + examples/websocket/app.ts | 1 + src/codegen/expressions/calls.ts | 5 + .../infrastructure/generator-context.ts | 17 +- src/codegen/llvm-generator.ts | 31 +- src/codegen/stdlib/http-server.ts | 12 + src/codegen/stdlib/mongoose.ts | 807 ------------------ src/compiler.ts | 8 +- src/native-compiler-lib.ts | 8 +- tests/fixtures/websocket/ws-server-basic.ts | 1 + tests/network.test.ts | 2 +- 13 files changed, 109 insertions(+), 832 deletions(-) delete mode 100644 src/codegen/stdlib/mongoose.ts diff --git a/c_bridges/lws-bridge.c b/c_bridges/lws-bridge.c index 2731eaa1..565dd463 100644 --- a/c_bridges/lws-bridge.c +++ b/c_bridges/lws-bridge.c @@ -136,12 +136,16 @@ static size_t base64_encode(const unsigned char *in, size_t len, char *out) { /* ---- uv helpers ---- */ +static void ws_format_conn_id(uv_tcp_t *h, char *buf, size_t sz) { + snprintf(buf, sz, "%p", (void *)h); +} + static void on_close(uv_handle_t *handle) { http_conn_t *conn = (http_conn_t *)handle; if (conn->type == CONN_WEBSOCKET) { ws_track_remove(&conn->handle); if (g_ws_handler) { - char *evt_mem = (char *)GC_malloc(16); + char *evt_mem = (char *)GC_malloc(24); char **evt = (char **)evt_mem; char *empty = (char *)GC_malloc_atomic(1); empty[0] = '\0'; @@ -149,6 +153,9 @@ static void on_close(uv_handle_t *handle) { char *close_str = (char *)GC_malloc_atomic(6); memcpy(close_str, "close", 6); evt[1] = close_str; + char *conn_id_str = (char *)GC_malloc_atomic(20); + ws_format_conn_id(&conn->handle, conn_id_str, 20); + evt[2] = conn_id_str; g_ws_handler(evt_mem); } } @@ -470,7 +477,7 @@ static void ws_handle_upgrade(http_conn_t *conn) { send_raw(&conn->handle, response, (size_t)rlen); if (g_ws_handler) { - char *evt_mem = (char *)GC_malloc(16); + char *evt_mem = (char *)GC_malloc(24); char **evt = (char **)evt_mem; char *empty = (char *)GC_malloc_atomic(1); empty[0] = '\0'; @@ -478,6 +485,9 @@ static void ws_handle_upgrade(http_conn_t *conn) { char *open_str = (char *)GC_malloc_atomic(5); memcpy(open_str, "open", 5); evt[1] = open_str; + char *conn_id_str = (char *)GC_malloc_atomic(20); + ws_format_conn_id(&conn->handle, conn_id_str, 20); + evt[2] = conn_id_str; char *reply = g_ws_handler(evt_mem); if (reply && reply[0]) { @@ -526,12 +536,15 @@ static void ws_process_frame(http_conn_t *conn) { memcpy(data, payload, payload_len); data[payload_len] = '\0'; - char *evt_mem = (char *)GC_malloc(16); + char *evt_mem = (char *)GC_malloc(24); char **evt = (char **)evt_mem; evt[0] = data; char *msg_str = (char *)GC_malloc_atomic(8); memcpy(msg_str, "message", 8); evt[1] = msg_str; + char *conn_id_str = (char *)GC_malloc_atomic(20); + ws_format_conn_id(&conn->handle, conn_id_str, 20); + evt[2] = conn_id_str; char *reply = g_ws_handler(evt_mem); if (reply && reply[0]) { @@ -787,3 +800,14 @@ void lws_bridge_ws_broadcast(const char *data, int len) { ws_send_frame(g_ws_conns[i], 0x1, data, (size_t)len); } } + +void lws_bridge_ws_send_to(const char *conn_id, const char *data, int len) { + unsigned long long val = strtoull(conn_id, NULL, 0); + uv_tcp_t *handle = (uv_tcp_t *)(uintptr_t)val; + for (int i = 0; i < g_ws_conn_count; i++) { + if (g_ws_conns[i] == handle) { + ws_send_frame(handle, 0x1, data, (size_t)len); + return; + } + } +} diff --git a/c_bridges/lws-bridge.h b/c_bridges/lws-bridge.h index 3c55011d..a5f16ab7 100644 --- a/c_bridges/lws-bridge.h +++ b/c_bridges/lws-bridge.h @@ -25,5 +25,6 @@ typedef char* (*lws_bridge_ws_handler)(void *event); int lws_bridge_serve(int port, lws_bridge_http_handler http_handler, lws_bridge_ws_handler ws_handler); void lws_bridge_ws_send(void *wsi, const char *data, int len); void lws_bridge_ws_broadcast(const char *data, int len); +void lws_bridge_ws_send_to(const char *conn_id, const char *data, int len); #endif diff --git a/docs/stdlib/http-server.md b/docs/stdlib/http-server.md index 6a6c0b59..44d10340 100644 --- a/docs/stdlib/http-server.md +++ b/docs/stdlib/http-server.md @@ -27,6 +27,7 @@ Any HTTP request with an `Upgrade: websocket` header is automatically upgraded w interface WsEvent { data: string; event: string; // "open", "message", "close" + connId: string; // hex pointer identifying this connection } function wsHandler(event: WsEvent): string { @@ -48,6 +49,20 @@ Send a message to all connected WebSocket clients. Only available when a wsHandl wsBroadcast("hello everyone"); ``` +## `wsSend(connId, message)` + +Send a message to a specific WebSocket connection identified by its `connId`. The `connId` is available on every `WsEvent`. + +```typescript +function wsHandler(event: WsEvent): string { + if (event.event == "message") { + wsSend(event.connId, "echo: " + event.data); // reply to sender only + return ""; + } + return ""; +} +``` + ## HttpRequest Object | Property | Type | Description | @@ -74,6 +89,7 @@ If `headers` contains a `Content-Type:` line, it overrides the auto-detected con |----------|------|-------------| | `data` | `string` | Message data (empty for open/close events) | | `event` | `string` | Event type: `"open"`, `"message"`, or `"close"` | +| `connId` | `string` | Hex pointer identifying this connection (use with `wsSend`) | ## Example @@ -114,6 +130,7 @@ A chat server with HTTP homepage and WebSocket messaging: interface WsEvent { data: string; event: string; + connId: string; } interface HttpRequest { @@ -225,6 +242,7 @@ function handleRequest(req: HttpRequest): HttpResponse { |-----|---------| | `httpServe()` | libuv TCP + picohttpparser (zero-copy HTTP parsing) | | `wsBroadcast()` | `lws_bridge_ws_broadcast()` to all tracked connections | +| `wsSend()` | `lws_bridge_ws_send_to()` — parses hex connId, sends to matching handle | | WebSocket upgrade | embedded SHA-1 + base64 handshake + frame parser | ## Transparent Compression diff --git a/examples/websocket/app.ts b/examples/websocket/app.ts index 1857d9ea..d6b0338f 100644 --- a/examples/websocket/app.ts +++ b/examples/websocket/app.ts @@ -10,6 +10,7 @@ const port = parseInt(parser.getOption("port")); interface WsEvent { data: string; event: string; + connId: string; } interface HttpRequest { diff --git a/src/codegen/expressions/calls.ts b/src/codegen/expressions/calls.ts index f4a9efa6..0f854526 100644 --- a/src/codegen/expressions/calls.ts +++ b/src/codegen/expressions/calls.ts @@ -92,6 +92,11 @@ export class CallExpressionGenerator { return this.ctx.generateWsBroadcast(expr, params); } + // Handle wsSend(connId, msg) - send message to a specific WebSocket connection + if (expr.name === "wsSend") { + return this.ctx.generateWsSend(expr, params); + } + // Handle setTimeout() - libuv timer (one-shot) if (expr.name === "setTimeout") { return this.generateSetTimeout(expr, params); diff --git a/src/codegen/infrastructure/generator-context.ts b/src/codegen/infrastructure/generator-context.ts index d96b349e..cec83ff0 100644 --- a/src/codegen/infrastructure/generator-context.ts +++ b/src/codegen/infrastructure/generator-context.ts @@ -767,7 +767,7 @@ export interface IGeneratorContext { setUsesArraySort(value: boolean): void; setUsesCrypto(value: boolean): void; setUsesJson(value: boolean): void; - setUsesMongoose(value: boolean): void; + setUsesHttpServer(value: boolean): void; setUsesMultipart(value: boolean): void; setUsesRegex(value: boolean): void; setUsesTestRunner(value: boolean): void; @@ -802,6 +802,11 @@ export interface IGeneratorContext { */ generateWsBroadcast(expr: CallNode, params: string[]): string; + /** + * Generate WebSocket targeted send to a specific connection + */ + generateWsSend(expr: CallNode, params: string[]): string; + /** * Look up an interface definition by name from the AST */ @@ -980,7 +985,7 @@ export class MockGeneratorContext implements IGeneratorContext { public usesUvHrtime: number = 0; public usesCrypto: number = 0; public usesJson: number = 0; - public usesMongoose: number = 0; + public usesHttpServer: number = 0; public usesRegex: number = 0; public usesTestRunner: number = 0; public usesAsyncFs: number = 0; @@ -1191,8 +1196,8 @@ export class MockGeneratorContext implements IGeneratorContext { setUsesJson(value: boolean): void { this.usesJson = value ? 1 : 0; } - setUsesMongoose(value: boolean): void { - this.usesMongoose = value ? 1 : 0; + setUsesHttpServer(value: boolean): void { + this.usesHttpServer = value ? 1 : 0; } setUsesMultipart(value: boolean): void { // no-op in mock @@ -1695,6 +1700,10 @@ export class MockGeneratorContext implements IGeneratorContext { return "0.0"; } + generateWsSend(_expr: CallNode, _params: string[]): string { + return "0.0"; + } + getInterfaceFromAST( name: string, ): { name: string; fields: { name: string; type: string }[] } | null { diff --git a/src/codegen/llvm-generator.ts b/src/codegen/llvm-generator.ts index f9643c83..f3d86dab 100644 --- a/src/codegen/llvm-generator.ts +++ b/src/codegen/llvm-generator.ts @@ -210,7 +210,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { public usesArraySort: number = 0; public usesCrypto: number = 0; public usesJson: number = 0; - public usesMongoose: number = 0; + public usesHttpServer: number = 0; public usesMultipart: number = 0; public usesRegex: number = 0; public usesTestRunner: number = 0; @@ -1035,11 +1035,11 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { public getUsesJson(): boolean { return this.usesJson !== 0; } - public setUsesMongoose(value: boolean): void { - this.usesMongoose = value ? 1 : 0; + public setUsesHttpServer(value: boolean): void { + this.usesHttpServer = value ? 1 : 0; } - public getUsesMongoose(): boolean { - return this.usesMongoose !== 0; + public getUsesHttpServer(): boolean { + return this.usesHttpServer !== 0; } public setUsesMultipart(value: boolean): void { this.usesMultipart = value ? 1 : 0; @@ -1449,7 +1449,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { this.usesArraySort = 0; this.usesCrypto = 0; this.usesJson = 0; - this.usesMongoose = 0; + this.usesHttpServer = 0; this.usesMultipart = 0; this.usesRegex = 0; this.usesTestRunner = 0; @@ -2478,6 +2478,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { if (wsHandler) { irParts.push("\n"); irParts.push(this.httpServerGen.generateWsBroadcastFunction()); + irParts.push(this.httpServerGen.generateWsSendToFunction()); } } @@ -2553,7 +2554,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { this.usesPromises || this.usesCurl || this.usesUvHrtime || - this.usesMongoose || + this.usesHttpServer || this.usesAsyncFs; const needsPromise = this.usesPromises || this.usesCurl || this.usesAsyncFs; @@ -2628,7 +2629,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { finalParts.push("\n"); } - if (this.usesMongoose) { + if (this.usesHttpServer) { const httpServerDecls = this.httpServerGen.generateDeclarations(); if (httpServerDecls) { finalParts.push(this.filterDuplicateDeclarations(httpServerDecls)); @@ -3573,7 +3574,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { // Track handler for http server event handler generation this.httpHandlers.push(handlerName); - this.usesMongoose = 1; + this.usesHttpServer = 1; if (expr.args.length >= 3) { const wsHandlerArg = expr.args[2]; @@ -3613,6 +3614,18 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { return "0.0"; } + public generateWsSend(expr: CallNode, params: string[]): string { + if (expr.args.length < 2) { + return this.emitError("wsSend() requires 2 arguments: connId, message", expr.loc); + } + const connIdValue = this.generateExpression(expr.args[0], params); + const msgValue = this.generateExpression(expr.args[1], params); + const len = this.nextTemp(); + this.emit(`${len} = call i64 @strlen(i8* ${msgValue})`); + this.emit(`call void @__ws_send_to(i8* ${connIdValue}, i8* ${msgValue}, i64 ${len})`); + return "0.0"; + } + public getInterfaceFromAST( name: string, ): { name: string; fields: { name: string; type: string }[] } | null { diff --git a/src/codegen/stdlib/http-server.ts b/src/codegen/stdlib/http-server.ts index 785fb606..f8cb3d1e 100644 --- a/src/codegen/stdlib/http-server.ts +++ b/src/codegen/stdlib/http-server.ts @@ -25,6 +25,7 @@ export class HttpServerGenerator { "declare i32 @lws_bridge_serve(i32, void (%struct.lws_bridge_request*, %struct.lws_bridge_response*)*, i8* (i8*)*)\n"; ir += "declare void @lws_bridge_ws_send(i8*, i8*, i32)\n"; ir += "declare void @lws_bridge_ws_broadcast(i8*, i32)\n"; + ir += "declare void @lws_bridge_ws_send_to(i8*, i8*, i32)\n"; ir += "\n"; return ir; @@ -127,6 +128,17 @@ export class HttpServerGenerator { return ir; } + generateWsSendToFunction(): string { + let ir = "; WebSocket targeted send to specific connection (via lws-bridge)\n"; + ir += "define void @__ws_send_to(i8* %conn_id, i8* %msg, i64 %len) {\n"; + ir += "entry:\n"; + ir += " %len32 = trunc i64 %len to i32\n"; + ir += " call void @lws_bridge_ws_send_to(i8* %conn_id, i8* %msg, i32 %len32)\n"; + ir += " ret void\n"; + ir += "}\n\n"; + return ir; + } + generateWsBroadcastFunction(): string { let ir = "; WebSocket broadcast to all connected clients (via lws-bridge)\n"; ir += "define void @__ws_broadcast(i8* %msg, i64 %len) {\n"; diff --git a/src/codegen/stdlib/mongoose.ts b/src/codegen/stdlib/mongoose.ts deleted file mode 100644 index b23b4b06..00000000 --- a/src/codegen/stdlib/mongoose.ts +++ /dev/null @@ -1,807 +0,0 @@ -/** - * Mongoose HTTP Server Runtime Generator - * - * Generates LLVM IR declarations and runtime code for the mongoose - * embedded HTTP server library. This replaces the hand-written POSIX - * socket code with a battle-tested C library. - * - * Mongoose handles: - * - Socket creation and management - * - HTTP request parsing - * - Response formatting - * - Event-driven I/O - * - WebSocket upgrade and messaging - * - * Library: mongoose (cesanta/mongoose) - * Location: /data/users/cssmith/git/mongoose/mongoose.c - */ -export class MongooseGenerator { - /** - * Generate external declarations for mongoose functions and types - */ - generateDeclarations(): string { - let ir = "; Mongoose HTTP server library declarations\n"; - ir += "; Battle-tested embedded web server\n\n"; - - ir += "; Mongoose manager structure (opaque, ~500 bytes)\n"; - ir += "%struct.mg_mgr = type { [512 x i8] }\n\n"; - - ir += "; Mongoose connection structure (opaque)\n"; - ir += "%struct.mg_connection = type opaque\n\n"; - - ir += "; Mongoose string (pointer + length)\n"; - ir += "%struct.mg_str = type { i8*, i64 }\n\n"; - - ir += "; Mongoose HTTP header (name + value)\n"; - ir += "%struct.mg_http_header = type { %struct.mg_str, %struct.mg_str }\n\n"; - - ir += "; Mongoose HTTP message structure\n"; - ir += "; Contains parsed HTTP request/response data\n"; - ir += "; Must match mongoose.h mg_http_message exactly\n"; - ir += "%struct.mg_http_message = type {\n"; - ir += " %struct.mg_str, ; 0: method (GET, POST, etc.)\n"; - ir += " %struct.mg_str, ; 1: uri\n"; - ir += " %struct.mg_str, ; 2: query\n"; - ir += " %struct.mg_str, ; 3: proto (HTTP/1.1)\n"; - ir += " [30 x %struct.mg_http_header], ; 4: headers (MG_MAX_HTTP_HEADERS = 30)\n"; - ir += " %struct.mg_str, ; 5: body\n"; - ir += " %struct.mg_str, ; 6: head (request line + headers)\n"; - ir += " %struct.mg_str ; 7: message (full raw message)\n"; - ir += "}\n\n"; - - ir += "; Mongoose WebSocket message structure\n"; - ir += "%struct.mg_ws_message = type { %struct.mg_str, i8 }\n\n"; - - ir += "; Core mongoose functions\n"; - ir += "declare void @mg_mgr_init(%struct.mg_mgr*)\n"; - ir += "declare void @mg_mgr_free(%struct.mg_mgr*)\n"; - ir += "declare void @mg_mgr_poll(%struct.mg_mgr*, i32)\n"; - ir += "\n"; - - ir += "; HTTP server functions\n"; - ir += - "declare %struct.mg_connection* @mg_http_listen(%struct.mg_mgr*, i8*, void (%struct.mg_connection*, i32, i8*, i8*)*, i8*)\n"; - ir += "declare void @mg_http_reply(%struct.mg_connection*, i32, i8*, i8*, ...)\n"; - ir += "declare i32 @mg_http_match_uri(%struct.mg_http_message*, i8*)\n"; - ir += "declare %struct.mg_str* @mg_http_get_header(%struct.mg_http_message*, i8*)\n"; - ir += "declare i64 @mg_printf(%struct.mg_connection*, i8*, ...)\n"; - ir += "declare i1 @mg_send(%struct.mg_connection*, i8*, i64)\n"; - ir += "\n"; - - ir += "; WebSocket functions\n"; - ir += "declare void @mg_ws_upgrade(%struct.mg_connection*, %struct.mg_http_message*, i8*)\n"; - ir += "declare i64 @mg_ws_send(%struct.mg_connection*, i8*, i64, i32)\n"; - ir += "\n"; - - ir += "; String utility functions\n"; - ir += "declare i32 @mg_strcmp(%struct.mg_str, %struct.mg_str)\n"; - ir += "declare i32 @mg_vcmp(%struct.mg_str*, i8*)\n"; - ir += "declare i8* @mg_mprintf(i8*, ...)\n"; - ir += "\n"; - - ir += "; Logging control\n"; - ir += "@mg_log_level = external global i32\n"; - ir += "\n"; - - ir += "; Mongoose event constants (from enum in mongoose.h)\n"; - ir += "@MG_EV_HTTP_MSG = private constant i32 11\n"; - ir += "\n"; - - ir += "; zlib compression functions\n"; - ir += "declare i32 @compress(i8*, i64*, i8*, i64)\n"; - ir += "declare i64 @compressBound(i64)\n"; - ir += "\n"; - - ir += "; zstd compression functions\n"; - ir += "declare i64 @ZSTD_compress(i8*, i64, i8*, i64, i32)\n"; - ir += "declare i64 @ZSTD_compressBound(i64)\n"; - ir += "declare i32 @ZSTD_isError(i64)\n"; - ir += "\n"; - - return ir; - } - - /** - * Generate the HTTP/WebSocket server event handler wrapper - * This bridges mongoose's C callback to ChadScript's handler functions - * - * HTTP handler receives a Request object: { method: string, path: string, body: string, contentType: string } - * HTTP handler returns a Response object: { status: number, body: string } - * - * WS handler receives a WsEvent object: { data: string, event: string } - * WS handler returns a string (sent back to sender; empty = no response) - */ - generateEventHandler(httpHandlerName: string, wsHandlerName?: string): string { - let ir = "; HTTP/WebSocket event handler wrapper for mongoose\n"; - ir += - `define void @__mg_http_handler(%struct.mg_connection* %conn, i32 %ev, i8* %ev_data, i8* %fn_data) {` + - "\n"; - ir += "entry:\n"; - - if (wsHandlerName) { - ir += " switch i32 %ev, label %done [\n"; - ir += " i32 11, label %handle_http\n"; - ir += " i32 12, label %handle_ws_open\n"; - ir += " i32 13, label %handle_ws_msg\n"; - ir += " i32 9, label %handle_close\n"; - ir += " ]\n\n"; - } else { - ir += " ; Check if this is an HTTP message event (MG_EV_HTTP_MSG = 11)\n"; - ir += " %ev_http = load i32, i32* @MG_EV_HTTP_MSG\n"; - ir += " %is_http = icmp eq i32 %ev, %ev_http\n"; - ir += " br i1 %is_http, label %handle_http, label %done\n\n"; - } - - ir += "handle_http:\n"; - - if (wsHandlerName) { - ir += " ; Check for Upgrade: websocket header\n"; - ir += " %hm_upgrade_check = bitcast i8* %ev_data to %struct.mg_http_message*\n"; - ir += - " %upgrade_hdr_name = getelementptr [8 x i8], [8 x i8]* @.str.upgrade_header, i32 0, i32 0\n"; - ir += - " %upgrade_ptr = call %struct.mg_str* @mg_http_get_header(%struct.mg_http_message* %hm_upgrade_check, i8* %upgrade_hdr_name)\n"; - ir += " %has_upgrade = icmp ne %struct.mg_str* %upgrade_ptr, null\n"; - ir += " br i1 %has_upgrade, label %do_ws_upgrade, label %handle_http_normal\n\n"; - - ir += "do_ws_upgrade:\n"; - ir += " %null_fmt = getelementptr [1 x i8], [1 x i8]* @.str.mongoose_empty, i32 0, i32 0\n"; - ir += - " call void @mg_ws_upgrade(%struct.mg_connection* %conn, %struct.mg_http_message* %hm_upgrade_check, i8* %null_fmt)\n"; - ir += " br label %done\n\n"; - - ir += "handle_http_normal:\n"; - } - - ir += " ; Cast ev_data to mg_http_message*\n"; - ir += " %hm = bitcast i8* %ev_data to %struct.mg_http_message*\n"; - ir += "\n"; - - ir += " ; Get method mg_str (first field: offset 0)\n"; - ir += - " %method_buf_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 0, i32 0\n"; - ir += " %method_buf = load i8*, i8** %method_buf_ptr\n"; - ir += - " %method_len_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 0, i32 1\n"; - ir += " %method_len = load i64, i64* %method_len_ptr\n"; - ir += "\n"; - - ir += " ; Get uri mg_str (second field: offset 1)\n"; - ir += - " %uri_buf_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 1, i32 0\n"; - ir += " %uri_buf = load i8*, i8** %uri_buf_ptr\n"; - ir += - " %uri_len_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 1, i32 1\n"; - ir += " %uri_len = load i64, i64* %uri_len_ptr\n"; - ir += "\n"; - - ir += " ; Get query mg_str (third field: offset 2)\n"; - ir += - " %query_buf_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 2, i32 0\n"; - ir += " %query_buf = load i8*, i8** %query_buf_ptr\n"; - ir += - " %query_len_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 2, i32 1\n"; - ir += " %query_len = load i64, i64* %query_len_ptr\n"; - ir += "\n"; - - ir += " ; Get body mg_str (field 5: after proto and headers)\n"; - ir += - " %body_buf_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 5, i32 0\n"; - ir += " %body_buf = load i8*, i8** %body_buf_ptr\n"; - ir += - " %body_len_ptr = getelementptr %struct.mg_http_message, %struct.mg_http_message* %hm, i32 0, i32 5, i32 1\n"; - ir += " %body_len = load i64, i64* %body_len_ptr\n"; - ir += "\n"; - - ir += " ; Allocate and copy method with null terminator\n"; - ir += " %method_alloc_size = add i64 %method_len, 1\n"; - ir += " %method = call i8* @GC_malloc_atomic(i64 %method_alloc_size)\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %method, i8* %method_buf, i64 %method_len, i1 false)\n"; - ir += " %method_null_pos = getelementptr i8, i8* %method, i64 %method_len\n"; - ir += " store i8 0, i8* %method_null_pos\n"; - ir += "\n"; - - ir += " ; Allocate and copy body with null terminator\n"; - ir += " %body_alloc_size = add i64 %body_len, 1\n"; - ir += " %body = call i8* @GC_malloc_atomic(i64 %body_alloc_size)\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %body, i8* %body_buf, i64 %body_len, i1 false)\n"; - ir += " %body_null_pos = getelementptr i8, i8* %body, i64 %body_len\n"; - ir += " store i8 0, i8* %body_null_pos\n"; - ir += "\n"; - - ir += " ; Get Content-Type header using mg_http_get_header\n"; - ir += - " %ct_header_name = getelementptr [13 x i8], [13 x i8]* @.str.content_type_header, i32 0, i32 0\n"; - ir += - " %ct_ptr = call %struct.mg_str* @mg_http_get_header(%struct.mg_http_message* %hm, i8* %ct_header_name)\n"; - ir += " %ct_found = icmp ne %struct.mg_str* %ct_ptr, null\n"; - ir += " br i1 %ct_found, label %get_ct_fields, label %use_empty_ct\n\n"; - - ir += "get_ct_fields:\n"; - ir += " %ct_buf_ptr = getelementptr %struct.mg_str, %struct.mg_str* %ct_ptr, i32 0, i32 0\n"; - ir += " %ct_buf = load i8*, i8** %ct_buf_ptr\n"; - ir += " %ct_len_ptr = getelementptr %struct.mg_str, %struct.mg_str* %ct_ptr, i32 0, i32 1\n"; - ir += " %ct_len = load i64, i64* %ct_len_ptr\n"; - ir += " %has_ct = icmp sgt i64 %ct_len, 0\n"; - ir += " br i1 %has_ct, label %copy_content_type, label %use_empty_ct\n\n"; - - ir += "copy_content_type:\n"; - ir += " %ct_alloc_size = add i64 %ct_len, 1\n"; - ir += " %ct_str = call i8* @GC_malloc_atomic(i64 %ct_alloc_size)\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %ct_str, i8* %ct_buf, i64 %ct_len, i1 false)\n"; - ir += " %ct_null_pos = getelementptr i8, i8* %ct_str, i64 %ct_len\n"; - ir += " store i8 0, i8* %ct_null_pos\n"; - ir += " br label %build_path\n\n"; - - ir += "use_empty_ct:\n"; - ir += " %empty_ct = getelementptr [1 x i8], [1 x i8]* @.str.mongoose_empty, i32 0, i32 0\n"; - ir += " br label %build_path\n\n"; - - ir += "build_path:\n"; - ir += - " %content_type_val = phi i8* [ %ct_str, %copy_content_type ], [ %empty_ct, %use_empty_ct ]\n"; - ir += "\n"; - - ir += " ; Check if there is a query string\n"; - ir += " %has_query = icmp sgt i64 %query_len, 0\n"; - ir += " br i1 %has_query, label %build_full_path, label %use_uri_only\n\n"; - - ir += "build_full_path:\n"; - ir += ' ; Build path = uri + "?" + query\n'; - ir += " %path_total_len = add i64 %uri_len, %query_len\n"; - ir += " %path_total_len2 = add i64 %path_total_len, 2\n"; - ir += " %path_full = call i8* @GC_malloc_atomic(i64 %path_total_len2)\n"; - ir += " ; Copy uri\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %path_full, i8* %uri_buf, i64 %uri_len, i1 false)\n"; - ir += ' ; Add "?"\n'; - ir += " %qmark_pos = getelementptr i8, i8* %path_full, i64 %uri_len\n"; - ir += " store i8 63, i8* %qmark_pos\n"; - ir += " ; Copy query\n"; - ir += " %query_start = add i64 %uri_len, 1\n"; - ir += " %query_dest = getelementptr i8, i8* %path_full, i64 %query_start\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %query_dest, i8* %query_buf, i64 %query_len, i1 false)\n"; - ir += " ; Null terminate\n"; - ir += " %full_path_end = add i64 %path_total_len, 1\n"; - ir += " %path_null_pos = getelementptr i8, i8* %path_full, i64 %full_path_end\n"; - ir += " store i8 0, i8* %path_null_pos\n"; - ir += " br label %call_handler\n\n"; - - ir += "use_uri_only:\n"; - ir += " ; Just copy uri without query\n"; - ir += " %uri_alloc_size = add i64 %uri_len, 1\n"; - ir += " %path_only = call i8* @GC_malloc_atomic(i64 %uri_alloc_size)\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %path_only, i8* %uri_buf, i64 %uri_len, i1 false)\n"; - ir += " %path_only_null = getelementptr i8, i8* %path_only, i64 %uri_len\n"; - ir += " store i8 0, i8* %path_only_null\n"; - ir += " br label %call_handler\n\n"; - - ir += "call_handler:\n"; - ir += " %path = phi i8* [ %path_full, %build_full_path ], [ %path_only, %use_uri_only ]\n"; - ir += "\n"; - - ir += " ; Build Request object: { i8* method, i8* path, i8* body, i8* contentType }\n"; - ir += " ; Allocate Request struct (4 pointers = 32 bytes)\n"; - ir += " %req_mem = call i8* @GC_malloc(i64 32)\n"; - ir += " %req_struct = bitcast i8* %req_mem to { i8*, i8*, i8*, i8* }*\n"; - ir += "\n"; - - ir += " ; Store method (field 0)\n"; - ir += - " %req_method_ptr = getelementptr { i8*, i8*, i8*, i8* }, { i8*, i8*, i8*, i8* }* %req_struct, i32 0, i32 0\n"; - ir += " store i8* %method, i8** %req_method_ptr\n"; - ir += "\n"; - - ir += " ; Store path (field 1)\n"; - ir += - " %req_path_ptr = getelementptr { i8*, i8*, i8*, i8* }, { i8*, i8*, i8*, i8* }* %req_struct, i32 0, i32 1\n"; - ir += " store i8* %path, i8** %req_path_ptr\n"; - ir += "\n"; - - ir += " ; Store body (field 2)\n"; - ir += - " %req_body_ptr = getelementptr { i8*, i8*, i8*, i8* }, { i8*, i8*, i8*, i8* }* %req_struct, i32 0, i32 2\n"; - ir += " store i8* %body, i8** %req_body_ptr\n"; - ir += "\n"; - - ir += " ; Store contentType (field 3)\n"; - ir += - " %req_ct_ptr = getelementptr { i8*, i8*, i8*, i8* }, { i8*, i8*, i8*, i8* }* %req_struct, i32 0, i32 3\n"; - ir += " store i8* %content_type_val, i8** %req_ct_ptr\n"; - ir += "\n"; - - ir += ` ; Call user handler: ${httpHandlerName}(request) -> Response object` + "\n"; - ir += ` ; Request struct layout: { i8* method, i8* path, i8* body, i8* contentType }` + "\n"; - ir += ` ; Response struct layout: { double status, i8* body }` + "\n"; - ir += ` %response_ptr = call i8* @${httpHandlerName}(i8* %req_mem)` + "\n"; - ir += "\n"; - - ir += " ; Cast response pointer to Response struct { double, i8* }\n"; - ir += " %response_struct = bitcast i8* %response_ptr to { double, i8* }*\n"; - ir += "\n"; - - ir += " ; Extract status code (field 0)\n"; - ir += - " %status_ptr = getelementptr { double, i8* }, { double, i8* }* %response_struct, i32 0, i32 0\n"; - ir += " %status_dbl = load double, double* %status_ptr\n"; - ir += " %status_code = fptosi double %status_dbl to i32\n"; - ir += "\n"; - - ir += " ; Extract body string (field 1)\n"; - ir += - " %body_ptr_loc = getelementptr { double, i8* }, { double, i8* }* %response_struct, i32 0, i32 1\n"; - ir += " %response_body = load i8*, i8** %body_ptr_loc\n"; - ir += "\n"; - - ir += " ; Auto-detect content type from body content\n"; - ir += " %body_first_byte = load i8, i8* %response_body\n"; - ir += " %is_lt = icmp eq i8 %body_first_byte, 60\n"; - ir += " br i1 %is_lt, label %ct_html, label %check_json\n\n"; - - ir += "check_json:\n"; - ir += " %is_lbrace = icmp eq i8 %body_first_byte, 123\n"; - ir += " %is_lbracket = icmp eq i8 %body_first_byte, 91\n"; - ir += " %is_json = or i1 %is_lbrace, %is_lbracket\n"; - ir += " br i1 %is_json, label %ct_json, label %ct_plain\n\n"; - - ir += "ct_html:\n"; - ir += " %html_ct = getelementptr [26 x i8], [26 x i8]* @.str.ct_html, i32 0, i32 0\n"; - ir += " br label %send_response\n\n"; - - ir += "ct_json:\n"; - ir += " %json_ct = getelementptr [33 x i8], [33 x i8]* @.str.ct_json, i32 0, i32 0\n"; - ir += " br label %send_response\n\n"; - - ir += "ct_plain:\n"; - ir += - " %plain_ct = getelementptr [27 x i8], [27 x i8]* @.str.content_type_text, i32 0, i32 0\n"; - ir += " br label %send_response\n\n"; - - ir += "send_response:\n"; - ir += - " %final_ct = phi i8* [ %html_ct, %ct_html ], [ %json_ct, %ct_json ], [ %plain_ct, %ct_plain ]\n"; - ir += "\n"; - - ir += " ; Get body length for compression check\n"; - ir += " %resp_body_len = call i64 @strlen(i8* %response_body)\n"; - ir += "\n"; - - ir += " ; Check for Accept-Encoding header\n"; - ir += - " %ae_header_name = getelementptr [16 x i8], [16 x i8]* @.str.accept_encoding_header, i32 0, i32 0\n"; - ir += - " %ae_ptr = call %struct.mg_str* @mg_http_get_header(%struct.mg_http_message* %hm, i8* %ae_header_name)\n"; - ir += " %ae_found = icmp ne %struct.mg_str* %ae_ptr, null\n"; - ir += " br i1 %ae_found, label %copy_ae, label %send_uncompressed\n\n"; - - ir += "copy_ae:\n"; - ir += " %ae_buf_ptr = getelementptr %struct.mg_str, %struct.mg_str* %ae_ptr, i32 0, i32 0\n"; - ir += " %ae_buf = load i8*, i8** %ae_buf_ptr\n"; - ir += " %ae_len_ptr = getelementptr %struct.mg_str, %struct.mg_str* %ae_ptr, i32 0, i32 1\n"; - ir += " %ae_len = load i64, i64* %ae_len_ptr\n"; - ir += " %ae_alloc = add i64 %ae_len, 1\n"; - ir += " %ae_str = call i8* @GC_malloc_atomic(i64 %ae_alloc)\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %ae_str, i8* %ae_buf, i64 %ae_len, i1 false)\n"; - ir += " %ae_null_pos = getelementptr i8, i8* %ae_str, i64 %ae_len\n"; - ir += " store i8 0, i8* %ae_null_pos\n"; - ir += " br label %check_ae_zstd\n\n"; - - ir += "check_ae_zstd:\n"; - ir += " %zstd_needle = getelementptr [5 x i8], [5 x i8]* @.str.zstd_needle, i32 0, i32 0\n"; - ir += " %has_zstd_ptr = call i8* @strstr(i8* %ae_str, i8* %zstd_needle)\n"; - ir += " %has_zstd = icmp ne i8* %has_zstd_ptr, null\n"; - ir += " br i1 %has_zstd, label %check_zstd_body_size, label %check_ae_deflate\n\n"; - - ir += "check_zstd_body_size:\n"; - ir += " %zstd_body_big = icmp ugt i64 %resp_body_len, 256\n"; - ir += " br i1 %zstd_body_big, label %do_zstd_compress, label %send_uncompressed\n\n"; - - ir += "do_zstd_compress:\n"; - ir += " %zstd_max = call i64 @ZSTD_compressBound(i64 %resp_body_len)\n"; - ir += " %zstd_buf = call i8* @GC_malloc_atomic(i64 %zstd_max)\n"; - ir += - " %zstd_result = call i64 @ZSTD_compress(i8* %zstd_buf, i64 %zstd_max, i8* %response_body, i64 %resp_body_len, i32 1)\n"; - ir += " %zstd_err = call i32 @ZSTD_isError(i64 %zstd_result)\n"; - ir += " %zstd_ok = icmp eq i32 %zstd_err, 0\n"; - ir += " br i1 %zstd_ok, label %check_zstd_ratio, label %check_ae_deflate\n\n"; - - ir += "check_zstd_ratio:\n"; - ir += " %zstd_smaller = icmp ult i64 %zstd_result, %resp_body_len\n"; - ir += " br i1 %zstd_smaller, label %send_zstd, label %check_ae_deflate\n\n"; - - ir += "send_zstd:\n"; - ir += " %ct_len_zstd = call i64 @strlen(i8* %final_ct)\n"; - ir += " %ce_zstd_hdr = getelementptr [25 x i8], [25 x i8]* @.str.ce_zstd, i32 0, i32 0\n"; - ir += " %ce_zstd_len = call i64 @strlen(i8* %ce_zstd_hdr)\n"; - ir += " %zstd_hdr_len = add i64 %ct_len_zstd, %ce_zstd_len\n"; - ir += " %zstd_hdr_alloc = add i64 %zstd_hdr_len, 1\n"; - ir += " %zstd_combined_hdr = call i8* @GC_malloc_atomic(i64 %zstd_hdr_alloc)\n"; - ir += " call i8* @strcpy(i8* %zstd_combined_hdr, i8* %final_ct)\n"; - ir += " call i8* @strcat(i8* %zstd_combined_hdr, i8* %ce_zstd_hdr)\n"; - ir += - " %zstd_resp_fmt = getelementptr [42 x i8], [42 x i8]* @.str.binary_resp_fmt, i32 0, i32 0\n"; - ir += - " call i64 (%struct.mg_connection*, i8*, ...) @mg_printf(%struct.mg_connection* %conn, i8* %zstd_resp_fmt, i32 %status_code, i8* %zstd_combined_hdr, i64 %zstd_result)\n"; - ir += " call i1 @mg_send(%struct.mg_connection* %conn, i8* %zstd_buf, i64 %zstd_result)\n"; - ir += " br label %done\n\n"; - - ir += "check_ae_deflate:\n"; - ir += - " %deflate_needle = getelementptr [8 x i8], [8 x i8]* @.str.deflate_needle, i32 0, i32 0\n"; - ir += " %has_deflate_ptr = call i8* @strstr(i8* %ae_str, i8* %deflate_needle)\n"; - ir += " %has_deflate = icmp ne i8* %has_deflate_ptr, null\n"; - ir += " br i1 %has_deflate, label %check_body_size, label %send_uncompressed\n\n"; - - ir += "check_body_size:\n"; - ir += " %body_big_enough = icmp ugt i64 %resp_body_len, 256\n"; - ir += " br i1 %body_big_enough, label %do_compress, label %send_uncompressed\n\n"; - - ir += "do_compress:\n"; - ir += " %max_compressed = call i64 @compressBound(i64 %resp_body_len)\n"; - ir += " %comp_buf = call i8* @GC_malloc_atomic(i64 %max_compressed)\n"; - ir += " %dest_len_ptr = alloca i64\n"; - ir += " store i64 %max_compressed, i64* %dest_len_ptr\n"; - ir += - " %comp_result = call i32 @compress(i8* %comp_buf, i64* %dest_len_ptr, i8* %response_body, i64 %resp_body_len)\n"; - ir += " %comp_ok = icmp eq i32 %comp_result, 0\n"; - ir += " br i1 %comp_ok, label %check_ratio, label %send_uncompressed\n\n"; - - ir += "check_ratio:\n"; - ir += " %compressed_len = load i64, i64* %dest_len_ptr\n"; - ir += " %is_smaller = icmp ult i64 %compressed_len, %resp_body_len\n"; - ir += " br i1 %is_smaller, label %send_compressed, label %send_uncompressed\n\n"; - - ir += "send_compressed:\n"; - ir += " %ct_len_comp = call i64 @strlen(i8* %final_ct)\n"; - ir += " %ce_hdr = getelementptr [28 x i8], [28 x i8]* @.str.ce_deflate, i32 0, i32 0\n"; - ir += " %ce_len = call i64 @strlen(i8* %ce_hdr)\n"; - ir += " %combined_hdr_len = add i64 %ct_len_comp, %ce_len\n"; - ir += " %combined_hdr_alloc = add i64 %combined_hdr_len, 1\n"; - ir += " %combined_hdr = call i8* @GC_malloc_atomic(i64 %combined_hdr_alloc)\n"; - ir += " call i8* @strcpy(i8* %combined_hdr, i8* %final_ct)\n"; - ir += " call i8* @strcat(i8* %combined_hdr, i8* %ce_hdr)\n"; - ir += - " %deflate_resp_fmt = getelementptr [42 x i8], [42 x i8]* @.str.binary_resp_fmt, i32 0, i32 0\n"; - ir += - " call i64 (%struct.mg_connection*, i8*, ...) @mg_printf(%struct.mg_connection* %conn, i8* %deflate_resp_fmt, i32 %status_code, i8* %combined_hdr, i64 %compressed_len)\n"; - ir += " call i1 @mg_send(%struct.mg_connection* %conn, i8* %comp_buf, i64 %compressed_len)\n"; - ir += " br label %done\n\n"; - - ir += "send_uncompressed:\n"; - ir += " %body_fmt = getelementptr [3 x i8], [3 x i8]* @.str.body_fmt, i32 0, i32 0\n"; - ir += - " call void (%struct.mg_connection*, i32, i8*, i8*, ...) @mg_http_reply(%struct.mg_connection* %conn, i32 %status_code, i8* %final_ct, i8* %body_fmt, i8* %response_body)\n"; - ir += "\n"; - - ir += " ; GC will handle cleanup of allocated strings\n"; - ir += " br label %done\n\n"; - - if (wsHandlerName) { - ir += this.generateWsOpenHandler(wsHandlerName); - ir += this.generateWsMsgHandler(wsHandlerName); - ir += this.generateWsCloseHandler(wsHandlerName); - } - - ir += "done:\n"; - ir += " ret void\n"; - ir += "}\n\n"; - - ir += - '@.str.content_type_text = private constant [27 x i8] c"Content-Type: text/plain\\0D\\0A\\00"\n'; - ir += '@.str.ct_html = private constant [26 x i8] c"Content-Type: text/html\\0D\\0A\\00"\n'; - ir += - '@.str.ct_json = private constant [33 x i8] c"Content-Type: application/json\\0D\\0A\\00"\n'; - ir += '@.str.body_fmt = private constant [3 x i8] c"%s\\00"\n'; - ir += '@.str.content_type_header = private constant [13 x i8] c"Content-Type\\00"\n'; - ir += '@.str.mongoose_empty = private constant [1 x i8] c"\\00"\n'; - ir += '@.str.accept_encoding_header = private constant [16 x i8] c"Accept-Encoding\\00"\n'; - ir += '@.str.deflate_needle = private constant [8 x i8] c"deflate\\00"\n'; - ir += - '@.str.ce_deflate = private constant [28 x i8] c"Content-Encoding: deflate\\0D\\0A\\00"\n'; - ir += '@.str.zstd_needle = private constant [5 x i8] c"zstd\\00"\n'; - ir += '@.str.ce_zstd = private constant [25 x i8] c"Content-Encoding: zstd\\0D\\0A\\00"\n'; - ir += - '@.str.binary_resp_fmt = private constant [42 x i8] c"HTTP/1.1 %d OK\\0D\\0A%sContent-Length: %ld\\0D\\0A\\0D\\0A\\00"\n'; - - if (wsHandlerName) { - ir += '@.str.upgrade_header = private constant [8 x i8] c"Upgrade\\00"\n'; - ir += '@.str.ws_event_open = private constant [5 x i8] c"open\\00"\n'; - ir += '@.str.ws_event_message = private constant [8 x i8] c"message\\00"\n'; - ir += '@.str.ws_event_close = private constant [6 x i8] c"close\\00"\n'; - } - - return ir; - } - - private generateWsOpenHandler(wsHandlerName: string): string { - let ir = "handle_ws_open:\n"; - ir += " call void @__ws_track_add(%struct.mg_connection* %conn)\n"; - ir += " %ws_open_evt_mem = call i8* @GC_malloc(i64 16)\n"; - ir += " %ws_open_evt = bitcast i8* %ws_open_evt_mem to { i8*, i8* }*\n"; - ir += - " %ws_open_data_ptr = getelementptr { i8*, i8* }, { i8*, i8* }* %ws_open_evt, i32 0, i32 0\n"; - ir += - " %ws_open_empty = getelementptr [1 x i8], [1 x i8]* @.str.mongoose_empty, i32 0, i32 0\n"; - ir += " store i8* %ws_open_empty, i8** %ws_open_data_ptr\n"; - ir += - " %ws_open_event_ptr = getelementptr { i8*, i8* }, { i8*, i8* }* %ws_open_evt, i32 0, i32 1\n"; - ir += - " %ws_open_event_str = getelementptr [5 x i8], [5 x i8]* @.str.ws_event_open, i32 0, i32 0\n"; - ir += " store i8* %ws_open_event_str, i8** %ws_open_event_ptr\n"; - ir += ` %ws_open_result = call i8* @${wsHandlerName}(i8* %ws_open_evt_mem)\n`; - ir += " %ws_open_first = load i8, i8* %ws_open_result\n"; - ir += " %ws_open_has_reply = icmp ne i8 %ws_open_first, 0\n"; - ir += " br i1 %ws_open_has_reply, label %ws_open_send, label %done\n\n"; - - ir += "ws_open_send:\n"; - ir += " %ws_open_len = call i64 @strlen(i8* %ws_open_result)\n"; - ir += - " call i64 @mg_ws_send(%struct.mg_connection* %conn, i8* %ws_open_result, i64 %ws_open_len, i32 1)\n"; - ir += " br label %done\n\n"; - - return ir; - } - - private generateWsMsgHandler(wsHandlerName: string): string { - let ir = "handle_ws_msg:\n"; - ir += " %wm = bitcast i8* %ev_data to %struct.mg_ws_message*\n"; - ir += - " %ws_data_str_ptr = getelementptr %struct.mg_ws_message, %struct.mg_ws_message* %wm, i32 0, i32 0, i32 0\n"; - ir += " %ws_data_buf = load i8*, i8** %ws_data_str_ptr\n"; - ir += - " %ws_data_len_ptr = getelementptr %struct.mg_ws_message, %struct.mg_ws_message* %wm, i32 0, i32 0, i32 1\n"; - ir += " %ws_data_len = load i64, i64* %ws_data_len_ptr\n"; - ir += " %ws_data_alloc = add i64 %ws_data_len, 1\n"; - ir += " %ws_data_copy = call i8* @GC_malloc_atomic(i64 %ws_data_alloc)\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %ws_data_copy, i8* %ws_data_buf, i64 %ws_data_len, i1 false)\n"; - ir += " %ws_data_null = getelementptr i8, i8* %ws_data_copy, i64 %ws_data_len\n"; - ir += " store i8 0, i8* %ws_data_null\n"; - ir += " %ws_msg_evt_mem = call i8* @GC_malloc(i64 16)\n"; - ir += " %ws_msg_evt = bitcast i8* %ws_msg_evt_mem to { i8*, i8* }*\n"; - ir += - " %ws_msg_data_ptr = getelementptr { i8*, i8* }, { i8*, i8* }* %ws_msg_evt, i32 0, i32 0\n"; - ir += " store i8* %ws_data_copy, i8** %ws_msg_data_ptr\n"; - ir += - " %ws_msg_event_ptr = getelementptr { i8*, i8* }, { i8*, i8* }* %ws_msg_evt, i32 0, i32 1\n"; - ir += - " %ws_msg_event_str = getelementptr [8 x i8], [8 x i8]* @.str.ws_event_message, i32 0, i32 0\n"; - ir += " store i8* %ws_msg_event_str, i8** %ws_msg_event_ptr\n"; - ir += ` %ws_msg_result = call i8* @${wsHandlerName}(i8* %ws_msg_evt_mem)\n`; - ir += " %ws_msg_first = load i8, i8* %ws_msg_result\n"; - ir += " %ws_msg_has_reply = icmp ne i8 %ws_msg_first, 0\n"; - ir += " br i1 %ws_msg_has_reply, label %ws_msg_send, label %done\n\n"; - - ir += "ws_msg_send:\n"; - ir += " %ws_msg_reply_len = call i64 @strlen(i8* %ws_msg_result)\n"; - ir += - " call i64 @mg_ws_send(%struct.mg_connection* %conn, i8* %ws_msg_result, i64 %ws_msg_reply_len, i32 1)\n"; - ir += " br label %done\n\n"; - - return ir; - } - - private generateWsCloseHandler(wsHandlerName: string): string { - let ir = "handle_close:\n"; - ir += " call void @__ws_track_remove(%struct.mg_connection* %conn)\n"; - ir += " %ws_close_evt_mem = call i8* @GC_malloc(i64 16)\n"; - ir += " %ws_close_evt = bitcast i8* %ws_close_evt_mem to { i8*, i8* }*\n"; - ir += - " %ws_close_data_ptr = getelementptr { i8*, i8* }, { i8*, i8* }* %ws_close_evt, i32 0, i32 0\n"; - ir += - " %ws_close_empty = getelementptr [1 x i8], [1 x i8]* @.str.mongoose_empty, i32 0, i32 0\n"; - ir += " store i8* %ws_close_empty, i8** %ws_close_data_ptr\n"; - ir += - " %ws_close_event_ptr = getelementptr { i8*, i8* }, { i8*, i8* }* %ws_close_evt, i32 0, i32 1\n"; - ir += - " %ws_close_event_str = getelementptr [6 x i8], [6 x i8]* @.str.ws_event_close, i32 0, i32 0\n"; - ir += " store i8* %ws_close_event_str, i8** %ws_close_event_ptr\n"; - ir += ` call i8* @${wsHandlerName}(i8* %ws_close_evt_mem)\n`; - ir += " br label %done\n\n"; - - return ir; - } - - generateWsConnectionTracking(): string { - let ir = "; WebSocket connection tracking\n"; - ir += "@__ws_conns = global %struct.mg_connection** null\n"; - ir += "@__ws_conn_count = global i32 0\n"; - ir += "@__ws_conn_capacity = global i32 0\n\n"; - - ir += "define void @__ws_track_add(%struct.mg_connection* %conn) {\n"; - ir += "entry:\n"; - ir += " %count = load i32, i32* @__ws_conn_count\n"; - ir += " %cap = load i32, i32* @__ws_conn_capacity\n"; - ir += " %need_grow = icmp sge i32 %count, %cap\n"; - ir += " br i1 %need_grow, label %grow, label %add\n\n"; - - ir += "grow:\n"; - ir += " %new_cap_base = icmp eq i32 %cap, 0\n"; - ir += " br i1 %new_cap_base, label %init_cap, label %double_cap\n\n"; - - ir += "init_cap:\n"; - ir += " br label %do_realloc\n\n"; - - ir += "double_cap:\n"; - ir += " %doubled = mul i32 %cap, 2\n"; - ir += " br label %do_realloc\n\n"; - - ir += "do_realloc:\n"; - ir += " %new_cap = phi i32 [ 16, %init_cap ], [ %doubled, %double_cap ]\n"; - ir += " %new_cap_i64 = zext i32 %new_cap to i64\n"; - ir += " %alloc_size = mul i64 %new_cap_i64, 8\n"; - ir += " %new_arr = call i8* @GC_malloc(i64 %alloc_size)\n"; - ir += " %new_arr_typed = bitcast i8* %new_arr to %struct.mg_connection**\n"; - ir += " %old_arr = load %struct.mg_connection**, %struct.mg_connection*** @__ws_conns\n"; - ir += " %old_is_null = icmp eq %struct.mg_connection** %old_arr, null\n"; - ir += " br i1 %old_is_null, label %store_new, label %copy_old\n\n"; - - ir += "copy_old:\n"; - ir += " %count_i64 = zext i32 %count to i64\n"; - ir += " %copy_size = mul i64 %count_i64, 8\n"; - ir += " %old_i8 = bitcast %struct.mg_connection** %old_arr to i8*\n"; - ir += - " call void @llvm.memcpy.p0i8.p0i8.i64(i8* %new_arr, i8* %old_i8, i64 %copy_size, i1 false)\n"; - ir += " br label %store_new\n\n"; - - ir += "store_new:\n"; - ir += " store %struct.mg_connection** %new_arr_typed, %struct.mg_connection*** @__ws_conns\n"; - ir += " store i32 %new_cap, i32* @__ws_conn_capacity\n"; - ir += " br label %add\n\n"; - - ir += "add:\n"; - ir += " %cur_count = load i32, i32* @__ws_conn_count\n"; - ir += " %arr = load %struct.mg_connection**, %struct.mg_connection*** @__ws_conns\n"; - ir += " %idx = zext i32 %cur_count to i64\n"; - ir += - " %slot = getelementptr %struct.mg_connection*, %struct.mg_connection** %arr, i64 %idx\n"; - ir += " store %struct.mg_connection* %conn, %struct.mg_connection** %slot\n"; - ir += " %new_count = add i32 %cur_count, 1\n"; - ir += " store i32 %new_count, i32* @__ws_conn_count\n"; - ir += " ret void\n"; - ir += "}\n\n"; - - ir += "define void @__ws_track_remove(%struct.mg_connection* %conn) {\n"; - ir += "entry:\n"; - ir += " %count = load i32, i32* @__ws_conn_count\n"; - ir += " %is_zero = icmp eq i32 %count, 0\n"; - ir += " br i1 %is_zero, label %ret, label %search_start\n\n"; - - ir += "search_start:\n"; - ir += " %arr = load %struct.mg_connection**, %struct.mg_connection*** @__ws_conns\n"; - ir += " br label %search_loop\n\n"; - - ir += "search_loop:\n"; - ir += " %i = phi i32 [ 0, %search_start ], [ %i_next, %search_continue ]\n"; - ir += " %done = icmp sge i32 %i, %count\n"; - ir += " br i1 %done, label %ret, label %search_check\n\n"; - - ir += "search_check:\n"; - ir += " %i_i64 = zext i32 %i to i64\n"; - ir += - " %slot = getelementptr %struct.mg_connection*, %struct.mg_connection** %arr, i64 %i_i64\n"; - ir += " %cur = load %struct.mg_connection*, %struct.mg_connection** %slot\n"; - ir += " %match = icmp eq %struct.mg_connection* %cur, %conn\n"; - ir += " br i1 %match, label %found, label %search_continue\n\n"; - - ir += "search_continue:\n"; - ir += " %i_next = add i32 %i, 1\n"; - ir += " br label %search_loop\n\n"; - - ir += "found:\n"; - ir += " %last_idx = sub i32 %count, 1\n"; - ir += " %last_i64 = zext i32 %last_idx to i64\n"; - ir += - " %last_slot = getelementptr %struct.mg_connection*, %struct.mg_connection** %arr, i64 %last_i64\n"; - ir += " %last_val = load %struct.mg_connection*, %struct.mg_connection** %last_slot\n"; - ir += " store %struct.mg_connection* %last_val, %struct.mg_connection** %slot\n"; - ir += " store i32 %last_idx, i32* @__ws_conn_count\n"; - ir += " br label %ret\n\n"; - - ir += "ret:\n"; - ir += " ret void\n"; - ir += "}\n\n"; - - return ir; - } - - generateWsBroadcastFunction(): string { - let ir = "; WebSocket broadcast to all connected clients\n"; - ir += "define void @__ws_broadcast(i8* %msg, i64 %len) {\n"; - ir += "entry:\n"; - ir += " %count = load i32, i32* @__ws_conn_count\n"; - ir += " %is_zero = icmp eq i32 %count, 0\n"; - ir += " br i1 %is_zero, label %ret, label %loop_start\n\n"; - - ir += "loop_start:\n"; - ir += " %arr = load %struct.mg_connection**, %struct.mg_connection*** @__ws_conns\n"; - ir += " br label %loop\n\n"; - - ir += "loop:\n"; - ir += " %i = phi i32 [ 0, %loop_start ], [ %i_next, %loop_body ]\n"; - ir += " %done = icmp sge i32 %i, %count\n"; - ir += " br i1 %done, label %ret, label %loop_body\n\n"; - - ir += "loop_body:\n"; - ir += " %i_i64 = zext i32 %i to i64\n"; - ir += - " %slot = getelementptr %struct.mg_connection*, %struct.mg_connection** %arr, i64 %i_i64\n"; - ir += " %conn = load %struct.mg_connection*, %struct.mg_connection** %slot\n"; - ir += " call i64 @mg_ws_send(%struct.mg_connection* %conn, i8* %msg, i64 %len, i32 1)\n"; - ir += " %i_next = add i32 %i, 1\n"; - ir += " br label %loop\n\n"; - - ir += "ret:\n"; - ir += " ret void\n"; - ir += "}\n\n"; - - return ir; - } - - /** - * Generate the main HTTP server function using mongoose - * This replaces the hand-written POSIX socket code - */ - generateHttpServeFunction(): string { - let ir = "; httpServe(port, handler) - Start HTTP server using mongoose\n"; - ir += "; Handler takes Request object (i8*) and returns Response object (i8*)\n"; - ir += "define i32 @http_serve(i32 %port, i8* (i8*)* %handler) {\n"; - ir += "entry:\n"; - ir += " ; Set log level to errors only (1 = MG_LL_ERROR)\n"; - ir += " ; 0 = none, 1 = error, 2 = info, 3 = debug\n"; - ir += " store i32 1, i32* @mg_log_level\n"; - ir += "\n"; - ir += " ; Allocate mongoose manager on stack\n"; - ir += " %mgr = alloca %struct.mg_mgr\n"; - ir += "\n"; - - ir += " ; Initialize manager\n"; - ir += " call void @mg_mgr_init(%struct.mg_mgr* %mgr)\n"; - ir += "\n"; - - ir += ' ; Build listen URL: "http://0.0.0.0:PORT"\n'; - ir += " %url_fmt = getelementptr [18 x i8], [18 x i8]* @.str.http_url_fmt, i32 0, i32 0\n"; - ir += " %url = call i8* (i8*, ...) @mg_mprintf(i8* %url_fmt, i32 %port)\n"; - ir += "\n"; - - ir += " ; Print startup message\n"; - ir += " %msg_fmt = getelementptr [34 x i8], [34 x i8]* @.str.http_listening, i32 0, i32 0\n"; - ir += " call i32 (i8*, ...) @printf(i8* %msg_fmt, i32 %port)\n"; - ir += "\n"; - - ir += " ; Start HTTP listener\n"; - ir += " %handler_ptr = bitcast i8* (i8*)* %handler to i8*\n"; - ir += - " %conn = call %struct.mg_connection* @mg_http_listen(%struct.mg_mgr* %mgr, i8* %url, void (%struct.mg_connection*, i32, i8*, i8*)* @__mg_http_handler, i8* %handler_ptr)\n"; - ir += "\n"; - - ir += " ; Check if listen succeeded\n"; - ir += " %conn_null = icmp eq %struct.mg_connection* %conn, null\n"; - ir += " br i1 %conn_null, label %error, label %event_loop\n\n"; - - ir += "event_loop:\n"; - ir += " ; Poll for events (1000ms timeout)\n"; - ir += " call void @mg_mgr_poll(%struct.mg_mgr* %mgr, i32 1000)\n"; - ir += " br label %event_loop\n\n"; - - ir += "error:\n"; - ir += " %err_fmt = getelementptr [30 x i8], [30 x i8]* @.str.http_error, i32 0, i32 0\n"; - ir += " call i32 (i8*, ...) @printf(i8* %err_fmt, i32 %port)\n"; - ir += " call void @mg_mgr_free(%struct.mg_mgr* %mgr)\n"; - ir += " ret i32 1\n"; - ir += "}\n\n"; - - ir += '@.str.http_url_fmt = private constant [18 x i8] c"http://0.0.0.0:%d\\00"\n'; - ir += - '@.str.http_listening = private constant [34 x i8] c"HTTP server listening on port %d\\0A\\00"\n'; - ir += '@.str.http_error = private constant [30 x i8] c"Failed to start server on %d\\0A\\00"\n'; - - return ir; - } -} diff --git a/src/compiler.ts b/src/compiler.ts index 6b49f5c2..719a9f03 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -352,7 +352,7 @@ export function compile( generator.usesPromises || generator.usesCurl || generator.usesUvHrtime || - generator.usesMongoose + generator.usesHttpServer ) { linkLibs += ` -L${uvPath} -luv`; } @@ -365,7 +365,7 @@ export function compile( if (generator.usesSqlite) { linkLibs += " -lsqlite3"; } - if (generator.usesMongoose) { + if (generator.usesHttpServer) { linkLibs += ` -lz -lzstd`; } @@ -386,7 +386,7 @@ export function compile( if (generator.usesSqlite) { linkLibs = `-L${brewPrefix}/sqlite/lib ` + linkLibs; } - if (generator.usesMongoose) { + if (generator.usesHttpServer) { linkLibs = `-L${brewPrefix}/zstd/lib ` + linkLibs; } const sdkPath = execSync("xcrun --show-sdk-path", { stdio: "pipe", encoding: "utf8" }).trim(); @@ -395,7 +395,7 @@ export function compile( } // Bridge object files - const lwsBridgeObj = generator.usesMongoose + const lwsBridgeObj = generator.usesHttpServer ? `${bridgePath}/lws-bridge.o ${picoPath}/picohttpparser.o ${bridgePath}/multipart-bridge.o` : ""; const regexBridgeObj = generator.usesRegex ? `${bridgePath}/regex-bridge.o` : ""; diff --git a/src/native-compiler-lib.ts b/src/native-compiler-lib.ts index edbbca42..fb587b64 100644 --- a/src/native-compiler-lib.ts +++ b/src/native-compiler-lib.ts @@ -405,7 +405,7 @@ export function compileNative(inputFile: string, outputFile: string): void { generator.getUsesPromises() || generator.getUsesCurl() || generator.getUsesUvHrtime() || - generator.getUsesMongoose() + generator.getUsesHttpServer() ) { linkLibs = "-L" + uvDir + " -luv " + linkLibs; } @@ -418,10 +418,10 @@ export function compileNative(inputFile: string, outputFile: string): void { if (generator.getUsesSqlite()) { linkLibs = "-lsqlite3 " + linkLibs; } - if (generator.getUsesMongoose()) { + if (generator.getUsesHttpServer()) { linkLibs = "-lz -lzstd " + linkLibs; } - const lwsBridgeObj = generator.getUsesMongoose() + const lwsBridgeObj = generator.getUsesHttpServer() ? effectiveBridgePath + "/lws-bridge.o " + effectivePicoPath + @@ -455,7 +455,7 @@ export function compileNative(inputFile: string, outputFile: string): void { if (fs.existsSync("/usr/local/opt/sqlite/lib")) linkLibs = "-L/usr/local/opt/sqlite/lib " + linkLibs; } - if (generator.getUsesMongoose()) { + if (generator.getUsesHttpServer()) { if (fs.existsSync("/opt/homebrew/opt/zstd/lib")) linkLibs = "-L/opt/homebrew/opt/zstd/lib " + linkLibs; if (fs.existsSync("/usr/local/opt/zstd/lib")) diff --git a/tests/fixtures/websocket/ws-server-basic.ts b/tests/fixtures/websocket/ws-server-basic.ts index 5230a976..0b291e79 100644 --- a/tests/fixtures/websocket/ws-server-basic.ts +++ b/tests/fixtures/websocket/ws-server-basic.ts @@ -1,6 +1,7 @@ interface WsEvent { data: string; event: string; + connId: string; } function wsHandler(event: WsEvent): string { diff --git a/tests/network.test.ts b/tests/network.test.ts index 49779d39..a25a1856 100644 --- a/tests/network.test.ts +++ b/tests/network.test.ts @@ -162,7 +162,7 @@ describe("Network Tests", () => { assert.ok(stdout.includes("TEST_PASSED"), "Promise.race test should pass"); }); - it("should run HTTP server using httpServe() and mongoose", async () => { + it("should run HTTP server using httpServe()", async () => { const testFile = "tests/fixtures/network/http-server-test.ts"; await execAsync(`node dist/chad-node.js build ${testFile}`); From e1afd5454e28573c9b4ee557fc92e545585a18bd Mon Sep 17 00:00:00 2001 From: cs01 Date: Fri, 27 Feb 2026 20:53:30 -0800 Subject: [PATCH 3/3] fix setUsesMongoose->setUsesHttpServer in MethodCallGeneratorContext interface --- src/codegen/expressions/method-calls.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/expressions/method-calls.ts b/src/codegen/expressions/method-calls.ts index a2ce4093..5d056124 100644 --- a/src/codegen/expressions/method-calls.ts +++ b/src/codegen/expressions/method-calls.ts @@ -195,7 +195,7 @@ export interface MethodCallGeneratorContext { setUsesConsoleTime(value: boolean): void; setUsesCrypto(value: boolean): void; setUsesJson(value: boolean): void; - setUsesMongoose(value: boolean): void; + setUsesHttpServer(value: boolean): void; setUsesMultipart(value: boolean): void; setUsesTestRunner(value: boolean): void; classGenGetFieldInfo(