Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6b09dad
Spec: arrays as references (#453, #477)
ASDAlexander77 Oct 3, 2026
4dd14ca
Plan: arrays as references (#453, #477); spec takes the plan's rulings
ASDAlexander77 Oct 3, 2026
6b5fc83
Lowering: one helper knows the array layout (array ops in LowerToLLVM…
ASDAlexander77 Oct 3, 2026
5eafedd
Lowering: casts, constants and ownership routines read arrays through…
ASDAlexander77 Oct 3, 2026
591b707
A destructuring rest element is a new array (#477)
ASDAlexander77 Oct 3, 2026
bea7503
An array is a reference to a header that never moves (#453)
ASDAlexander77 Oct 3, 2026
07c4070
A declared array with no initializer starts as no array (#453)
ASDAlexander77 Oct 3, 2026
4f76d12
A view retains what it copies; capacity follows length (#453)
ASDAlexander77 Oct 3, 2026
aab5e26
The view test states how many elements each view has (#453)
ASDAlexander77 Oct 3, 2026
18c1419
Merge remote-tracking branch 'origin/main' into array-reference-switch
ASDAlexander77 Oct 3, 2026
e212487
main's argv is Ref<string> only; a string[] argv is a compile error
ASDAlexander77 Oct 3, 2026
3908d65
A string[] argv of main is refused ahead of time too (not in a DLL)
ASDAlexander77 Oct 3, 2026
b276090
Debug info: an array is a pointer to { data, length, capacity }
ASDAlexander77 Oct 3, 2026
483ea95
own: a push through a borrowed array parameter is the owner's (#453)
ASDAlexander77 Oct 3, 2026
88b48dd
The argv DLL test links from the build tree, not the environment
ASDAlexander77 Oct 3, 2026
d9248e7
rc: the header holds a counted reference to its data block
ASDAlexander77 Oct 4, 2026
7ab0f38
own: the header's release frees its data block outright, as before
ASDAlexander77 Oct 4, 2026
db6a74b
Spec and MLIRGen comment: the rest copy is an element copy, not a pus…
ASDAlexander77 Oct 4, 2026
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
62 changes: 6 additions & 56 deletions tslang/docs/superpowers/plans/2026-10-03-array-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
## Rulings (spec gaps decided while planning)

- **R1, a null header is an empty array.** Today zeroed memory is a valid empty array: elements of `new Array<T[]>(n)`, class fields of array type with no initializer, and `undefined` cast to an array (`CastLogicHelper::castToArrayType`, `isUndef`). So: reading `data`/`length` of a null header gives null/0; an op that changes an array through its slot (`push`, `pop`, `shift`, `unshift`, `splice`, `length =`) first stores a fresh empty header into a slot that holds null; rc/own release of null does nothing (`emitIfLastReference` already skips null). Cost if wrong: one compare and branch per array read.
- **R2, `main`'s `argv: string[]` is built by a new op, not a C adapter.** Spec §5 says lowering emits a C `main(int, char **)` adapter. The array must come from the model's allocator and be seen by the ownership passes as fresh, which only MLIR-level code gets. So MLIRGen gives `main` a `Ref<string>` second parameter and binds `argv` to a new `ts.ArrayFromCStrings(argc, argv)` op (a copy of each C string into an array). The JIT thunk's pointer branch and the C runtime then pass `char **` as they already can. Cost if wrong: one more op to teach to two passes.
- **R2 (replaced 2026-10-04), `main`'s argv is `Ref<string>` only.** `main`'s argv is `Ref<string>` only (C's `char **`); a `string[]` argv is a compile error that names the `Ref<string>` form (owner's ruling 2026-10-04; replaces plan ruling R2). Spec section 5 has the same text. The original Task 5 (a `ts.ArrayFromCStrings` op) is dropped.
- **R3, the rest copy is built in MLIRGen.** Spec §3 says `ts.ArrayView` becomes a copy. A copy must own its elements under rc/own; MLIRGen's existing "fresh array + synthesized loop" idiom (`MLIRGenCast.cpp`, numeric array widening) gets that from the passes for free. So MLIRGen stops emitting `ts.ArrayView` for a rest element and builds `const .rest: T[] = []; for (let .i = index; .i < .src.length; .i++) .rest.push(.src[.i]);` instead. `ArrayViewOpLowering` stays for other users (none known; see Task 3 step 1).

## Review Focus
Expand Down Expand Up @@ -769,63 +769,13 @@ header before a change through its slot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```

### Task 5: `main(argc, argv: string[])`
### Task 5: `main`'s argv is `Ref<string>` only

**Files:**
- Modify: `tslang/include/TypeScript/TypeScriptOps.td` (new op; build with `--parallel 3` - editing this file needs `-j 3`/`-j 4` or MSVC fails with C1060)
- Modify: `tslang/lib/TypeScript/MLIRGenFunctions.cpp` (`mlirGenParameters` ~192, `mlirGenFunctionParams` ~1360)
- Modify: `tslang/lib/TypeScript/LowerToLLVM.cpp` (new lowering pattern; add to the pattern list ~8140)
- Modify: `tslang/lib/TypeScript/LowerToAffineLoops.cpp` (~2660 legal-op list)
- Modify: `tslang/lib/TypeScript/OwnershipFacts.h` (~298 fresh-op list)
- Create: `tslang/test/tester/tests/00main_argv_array.ts`; Modify: `tslang/test/tester/CMakeLists.txt`

**Interfaces:**
- Consumes: `ArrayLayout::make`.
- Produces: `ts.ArrayFromCStrings`.

- [ ] **Step 1: Write the failing test** `00main_argv_array.ts`:

```ts
// `argv` as a `string[]`: an array of the program's arguments, each a copy of the C string, made
// with the memory model's allocator. argv[0] is the program (the input file under the JIT).
function main(argc: int, argv: string[]): int {
assert(argv.length == argc, "argv.length");
assert(argv.length >= 1 && argv[0].length > 0, "argv[0]");
argv.push("extra");
assert(argv.length == argc + 1, "argv is an ordinary array");
print("done.");
return 0;
}
```

Run under JIT and compiled (`test-runner` style): `tslang.exe --emit=jit --no-default-lib tslang/test/tester/tests/00main_argv_array.ts`. Expected now: crash or assertion failure (Task 4 removed the struct branch).

- [ ] **Step 2: The op** (`TypeScriptOps.td`, next to `TypeScript_StringCopyOp`):

```tablegen
// `main(argc, argv: string[])`: an array holding a copy of each of C's `argc` strings at `argv`
def TypeScript_ArrayFromCStringsOp : TypeScript_Op<"ArrayFromCStrings"> {
let arguments = (ins Index:$count, TypeScript_Ref:$argv);
let results = (outs Res<TypeScript_Array, "", [MemAlloc]>:$result);
}
```

Use the operand constraint other ops use for a `Ref` operand (look at how `TypeScript_RefType`/`AnyRef` is spelled in the file). Add `mlir_ts::ArrayFromCStringsOp` to the legal-op list in `LowerToAffineLoops.cpp` (~2660) and to the fresh-op `isa<...>` in `OwnershipFacts.h` (~298).

- [ ] **Step 3: Lowering.** A pattern in `LowerToLLVM.cpp`: allocate `count` pointers (`ch.MemoryAlloc(count * sizeof(ptr), MemoryAllocSet::Zero)`), loop `i` in `[0, count)` storing a copy of `argv[i]` (the same `strlen + 1` / `MemoryAlloc` / `MemcpyOp` sequence as `StringCopyOpLowering`, ~920; extract that lambda into a shared static function rather than duplicating it), then `ArrayLayout::make(arrayType, data, count)`. Build the loop with blocks as `OwnershipRoutineLogic::emitCountedLoop` does (or reuse it if reachable).

- [ ] **Step 4: MLIRGen.** In `mlirGenParameters`, when the function is `MAIN_ENTRY_NAME`, has exactly two parameters and the second's type is `ArrayType` of `StringType`: give the second parameter the type `mlir_ts::RefType::get(getStringType())` and the name `.argv`, and remember (in the `FunctionParamDOM` or a flag on the prototype) that `argv` must be made. In `mlirGenFunctionParams`, after the loop, for that function: load `argc` (cast to `index` with `mlir_ts::CastOp`), load `.argv`, create `ts.ArrayFromCStrings`, and `DECLARE` a `VariableDeclarationDOM` named with the original parameter name, type `string[]`, holding the op's result. `LowerToAffineLoops`'s shape check already accepts `Ref<string>` for argv; the JIT thunk's pointer branch passes C's `char **`; the C runtime does too when compiled.

- [ ] **Step 5: Run.**

```bash
cmake --build __build/tslang/windows-msbuild-2026-release --config Release --parallel 3 --target tslang 2>&1 | grep -E "error C|error:" | head
for mm in gc rc none own; do __build/tslang/windows-msbuild-2026-release/bin/tslang.exe --emit=jit --no-default-lib -mm=$mm tslang/test/tester/tests/00main_argv_array.ts; echo "$mm exit=$?"; done
```

Expected: `done.` and exit 0 for every model. Register it with `python "$R" main-argv-array 00main_argv_array.ts 00array7.ts 00array7.ts` (the corpora run it under rc and none the same way; if a corpus run fails only because it passes no arguments, remove the corpus line by hand and say so in the report). Also run `00main_argc_argv.ts` (the `Ref<string>` form) to confirm it still passes.
Revised 2026-10-04 (owner's ruling; the `ts.ArrayFromCStrings` op is dropped). A `main` whose second parameter is `string[]` would read C's `char **` as an array header, so it becomes a compile error:

- [ ] **Step 6: Full suite, commit** (`git commit -m "main(argc, argv: string[]) gets an array of copies of the C strings ..."`; mention that compiled `argv.length` was C's `envp` before).
- `LowerToAffineLoops.cpp`: in every build but a DLL (JIT and AOT) an array argv of `main` is an error with a source location, with the message `'main' takes argv as Ref<string> (C's char **), not string[]; read an argument with Deref(argv[i])`.
- `jit.cpp` `addEntryThunk`: comment and `unsupported()` message name `Ref<string>` only; no behaviour change.
- Test `tslang/test/tester/lowering-errors/main_argv_string_array.ts`, registered in `tslang/test/tester/CMakeLists.txt` for `--emit=jit`, `--emit=exe` and `--emit=obj` (and as a no-error case for `--emit=dll`), passes only when the compile output contains `takes argv as Ref<string>`. `tests/00main_argc_argv.ts` (the `Ref<string>` form) keeps passing.

### Task 6: debug info

Expand Down
27 changes: 19 additions & 8 deletions tslang/docs/superpowers/specs/2026-10-03-array-reference-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,17 @@ struct fails. Under rc a parameter push exits with 127 and prints nothing.
`undefined` cast to an array) reads as an empty array, and an op that changes an array through
its slot stores a fresh empty header into a slot holding null first. This keeps today's
behaviour, where a zeroed `{ data, length }` is an empty array (plan ruling R1).
- A null slot passed by value to a parameter (an unset field, an element made by `length =`):
a push through the parameter materialises a header in the parameter's copy, so the slot's
owner does not see the change and, under rc and own, that header leaks. Accepted: such a slot
holds `undefined` in TypeScript terms, where `.push` would throw (ruling T4-D).
- `ConstArray` (a literal's static data) is unchanged. The cast from `ConstArray` to `T[]` makes a
header and copies the data into a fresh block on both of its paths; the path that today points
the struct at the static data (`byValue = false`) copies as well, since `push` would otherwise
reallocate static memory.
- `[a, ...rest] = src` gives `rest` a new array holding a copy of the slice (#477). MLIRGen builds
it from a new empty array and a loop of pushes instead of emitting `ts.ArrayView`, so the
it as a new array of the slice's length and a loop that copies each element (ruling T3-A; the
plan had a loop of pushes) instead of emitting `ts.ArrayView`, so the
ownership passes see a fresh array (plan ruling R3).
- Layout: an array field, element, tuple slot or union payload shrinks from two words to one
pointer; `T[] | undefined` is the pointer and its tag.
Expand All @@ -92,8 +97,18 @@ unchanged.
- **none.** As today: nothing is freed. The header is one more allocation.
- **rc.** The counted block is the header.
- Retain increments the header's count (today: the data block's).
- Release, on the last reference: release elements `[0, length)`, free the data block, free
the header. The data block's own count is never read: the header holds its only reference.
- The header holds one counted reference to its data block (the block is given a count of 1
when the header is made over it, or when an empty array's first change allocates it).
- Release, on the header's last reference: drop the header's reference to the data block and
free the header; on the data block's last reference, release elements `[0, length)` and free
the block.
- Amended in Task 7: the first version freed the data block outright ("the header holds its
only reference"). The default library makes strings over an array's data block
(`<string><Opaque>Ref(buffer[0])` in `convertNumber`, `convertInteger` and the date
formatters), and such a string takes a reference to the block; under rc the block was freed
under it (five default-library tests failed ahead of time). Counting the header's reference
restores what main does. A reallocating change (`push`, and PR 3's growth) still moves the
block under such a string, as it does on main.
- Slots `[length, capacity)` are never released (and, from PR 3, hold zero).
- `pop` and `shift` hand the removed element's reference to the caller, as today.
- **own.** The header is a single-owned block, like a class instance.
Expand Down Expand Up @@ -121,11 +136,7 @@ unchanged.
the retain/release routines (§4).
- **Debug info** (`LLVMDebugInfo.h`): an array's debug type becomes a pointer to a
`{ data, length, capacity }` struct, so a debugger still shows the elements.
- **`main(argc, argv: string[])`:** MLIRGen gives `main` a `Ref<string>` second parameter, so the
entry point takes C's `char **`, and binds `argv` to a new op `ts.ArrayFromCStrings(argc, argv)`
that makes the array with the model's allocator, copying each string (plan ruling R2). Today
the struct is read from C's `argv` and `envp`, which is the open `argv.length` = envp bug; this
fixes it. A `Ref<string>` argv still receives C's `char **` directly. Both AOT and JIT.
- `main`'s argv is `Ref<string>` only (C's `char **`); a `string[]` argv is a compile error that names the `Ref<string>` form (owner's ruling 2026-10-04; replaces plan ruling R2).
- **32-bit x86:** the header is three pointer-sized words; nothing is specific to x86.

## 6. Growth (PR 3)
Expand Down
1 change: 1 addition & 0 deletions tslang/include/TypeScript/Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@

#define ARRAY_DATA_INDEX 0
#define ARRAY_SIZE_INDEX 1
#define ARRAY_CAPACITY_INDEX 2

#define OPTIONAL_VALUE_INDEX 0
#define OPTIONAL_HASVALUE_INDEX 1
Expand Down
Loading
Loading