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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ Two Rust rules are ported as *inference*, never as annotation:
Both are intra-module facts. A callee without facts - declared, imported from a module that
does not export them, a virtual call whose candidates disagree - defaults to borrowed parameters
and an owned result, the leak-side default `OwnedReturnConsumptionPass` already uses. A module
built under `own` exports its facts beside the existing `__tsmm_own_*` marker.
built under `own` exports one fact, `__own_no_drops`, beside the existing `__tsmm_own_*` marker
(§15.8); the two above stay intra-module.

### 2.5 Closures

Expand Down Expand Up @@ -146,9 +147,10 @@ Computes the interprocedural facts of §2.4 and pins them on each `FuncOp` as at
- `__own_result_borrows`: the parameter index (or `this`) the result borrows from, if any.

Runs to a fixpoint over the call graph (a function that returns what a callee returns depends
on the callee's fact). Unknown callees default as in §2.4. For a `-shared` build the facts are
serialised into an exported symbol beside the marker; the importer reads them during the symbol
enumeration it already does for `__tsmm_` and applies them to the declared `FuncOp`s.
on the callee's fact). Unknown callees default as in §2.4. A library built under own exports
`__own_no_drops` into a symbol beside the marker, and the importer reads it during the symbol
enumeration it already does for `__tsmm_` (§15.8). The other two facts are not exported: a module
linking the library statically sees none, and a caller that does not know them frees twice.

### 3.3 `OwnershipInferencePass` (per `ts.FuncOp`)

Expand Down Expand Up @@ -296,7 +298,7 @@ Each phase is a PR series leaving `main` green.
and reads; `any` moved to phase 4.
4. **Signature pass**: owned-by-callee parameters, borrowed-from-argument results, export beside
the marker and import on the other side. As built (§15): the facts, drop-free callees and
`any`; export and import deferred.
`any`; only `__own_no_drops` is exported and imported (§15.8).
5. **Closures**: escaping vs non-escaping, cells.
6. **Corpus report and diagnostics polish**: refine the shapes the histogram says matter.
7. **Generators and async**: the state object as owner.
Expand Down Expand Up @@ -924,11 +926,48 @@ Corpus (`test/tester/tests/*.ts` under `-mm=own --no-default-lib`): 272 of 564 c
the new ones throw or catch (the copy thunks); several more cast out of `any`. Release suite: 3226
of 3226.

### 15.8 Export and import of `__own_no_drops`

A library built under own writes, beside its marker `__tsmm_own_<module>`, a second exported string
`__tsown_<module>`. It lists the exported functions that destroy nothing a caller can reach, one
name per line, by the name they are exported under. The signature pass writes it: it is a copy of
the marker global under another name and value. So it is exported exactly as the marker is on
every platform, and MLIRGen emits the same under own as under rc (`test-own-mlirgen-matches-rc`).

The importer reads it in `mlirGenImportSharedLib`, beside the marker, from the loaded library or
from the file. It keeps the names on its module as `ts.own_imported_no_drops`. That happens in every
model, for the same reason, and only own's signature pass reads it. A call resolves to such a
function in either of two forms:

- a declaration linked through the import library;
- `ts.Load(ts.AddressOf @f)`, where the global `f` is `SearchForAddressOfSymbol("<name>")` (a
library loaded at run time).

The call is then known and drop-free, like the `.instanceOf` slot. A virtual call to a method of
an imported class stays unknown, because an override may be defined in the importer.

Only this fact crosses, and only in one direction of trust:

- A missing `__own_no_drops` only makes the importer report more. So a module that links the
library statically is sound without it: it re-parses the library's source and sees no facts.
- `__own_params` and `__own_result_borrows` change the callee's body: its retains are gone. An
importer that does not know them releases a moved argument or frees a borrowed result. The static
importer above is such an importer, and under own there is no counting to adapt a call with.

Tests: `test-jit-own-shared-no-drops` and `test-compile-own-shared-no-drops` run
`import_own_no_drops.ts` against `export_own_no_drops.ts` as a `-shared` pair. The importer holds a
borrow of a parameter's field across calls to `total` and `M.first`. On Windows,
`own-shared-no-drops.cmake` also checks two things. `import_own_err_imported_drops.ts` is still
rejected, because `shrink` removes an element and is not listed. And the same program is rejected
against the library built under rc, which lists nothing: that is the fact's teeth.

### 15.7 Known limits (the input to phase 5 and later)

- Export and import of the facts (§3.2). An exported function, or a method of an exported class,
gets no owned or borrowed facts, and a virtual call on one gets no `__own_no_drops`. That is sound
and restrictive for `-shared` modules.
- An exported function, or a method of an exported class, gets no owned or borrowed facts (§15.8
says why they cannot be exported), and a virtual call on one gets no `__own_no_drops`. That is
sound and restrictive for `-shared` modules.
- An importer under own cannot yet build an object of an imported class: `new H()` through the
library reports `'this value' is used here after its value was moved`.
- A parameter kept on some paths only (needs drop elaboration: a release on the others).
- A borrowed result's places are a wildcard, so any field overwrite between the call and the use
drops it, even of an unrelated object.
Expand Down
9 changes: 9 additions & 0 deletions tslang/include/TypeScript/Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@
// see docs/reference-counting-evaluation.md section 4. A missing marker means a module built
// before this existed, which is always garbage-collected.
#define SHARED_LIB_MEMORY_MODEL "__tsmm_"
// A library built under -mm=own also exports "__tsown_<file>_<hash>": the names of its exported
// functions that destroy nothing a caller can reach (`__own_no_drops`), one per line. The importer
// reads it beside the marker and keeps the names on its module (SHARED_LIB_OWN_NO_DROPS_ATTR_NAME),
// for the ownership signature pass. Only this fact crosses: a missing one just makes an importer
// under own report more, while the facts a callee's body relies on cannot be seen by an importer
// that re-parses source, and would free twice there.
#define SHARED_LIB_OWN_FACTS "__tsown_"
// a module attribute, so it has to carry the dialect's prefix
#define SHARED_LIB_OWN_NO_DROPS_ATTR_NAME "ts.own_imported_no_drops"
#define DLL_EXPORT "dllexport"
#define DLL_IMPORT "dllimport"
#define DLL_NAME "dllname"
Expand Down
2 changes: 2 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ class MLIRGenImpl

mlir::LogicalResult mlirGenImportSharedLib(mlir::Location location, StringRef filePath, bool dynamic, const GenContext &genContext);

void addImportedOwnNoDrops(StringRef factsText);

mlir::LogicalResult mlirGen(ImportDeclaration importDeclarationAST, const GenContext &genContext);

mlir::LogicalResult mlirGenImportBindings(ImportClause importClause);
Expand Down
65 changes: 65 additions & 0 deletions tslang/lib/TypeScript/MLIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,38 @@ namespace mlirgen
return mlir::success();
}

// The names in a library's SHARED_LIB_OWN_FACTS text, one per line, added to the ones this
// module already has from other libraries.
void MLIRGenImpl::addImportedOwnNoDrops(StringRef factsText)
{
llvm::SmallVector<mlir::Attribute> names;
llvm::StringSet<> seen;
if (auto existing = theModule->getAttrOfType<mlir::ArrayAttr>(SHARED_LIB_OWN_NO_DROPS_ATTR_NAME))
{
for (auto name : existing.getAsRange<mlir::StringAttr>())
{
seen.insert(name.getValue());
names.push_back(name);
}
}

llvm::SmallVector<StringRef> lines;
factsText.split(lines, '\n', -1, false);
for (auto line : lines)
{
line = line.trim();
if (!line.empty() && seen.insert(line).second)
{
names.push_back(builder.getStringAttr(line));
}
}

if (!names.empty())
{
theModule->setAttr(SHARED_LIB_OWN_NO_DROPS_ATTR_NAME, builder.getArrayAttr(names));
}
}

mlir::LogicalResult MLIRGenImpl::createGenericClassDeclarationExportGlobalVar(const GenContext &genContext)
{
if (!genericDeclExports.rdbuf()->in_avail() || !compileOptions.embedExportDeclarations)
Expand Down Expand Up @@ -1249,6 +1281,8 @@ namespace mlirgen
StringRef mlirGctors;
// every symbol the library exports
SmallVector<StringRef> symbolsAll;
// "__tsown_<file>_<hash>", one per module in the library built under own
SmallVector<StringRef> ownFactsSymbols;
#ifndef GENERATE_IMPORT_INFO_USING_D_TS_FILE
// loading Binary to get list of symbols
Dump::getSymbols(filePath, symbolsAll, stringAllocator);
Expand All @@ -1264,6 +1298,10 @@ namespace mlirgen
{
memoryModelSymbol = symbol;
}
else if (symbol.starts_with(SHARED_LIB_OWN_FACTS))
{
ownFactsSymbols.push_back(symbol);
}
else if (symbol == MLIR_GCTORS)
{
mlirGctors = symbol;
Expand Down Expand Up @@ -1453,6 +1491,33 @@ namespace mlirgen
}
}

// What a library built under own says about its functions (SHARED_LIB_OWN_FACTS). Kept in
// every model, so MLIRGen emits the same under own as under rc; only own reads it.
for (auto factsSymbol : ownFactsSymbols)
{
std::optional<std::string> factsText;
if (loadIntoCompiler)
{
if (auto addrOfFacts = dynLib.getAddressOfSymbol(factsSymbol.str().c_str()))
{
factsText = std::string(*(const char **)addrOfFacts);
}
}
else
{
factsText = Dump::readExportedCString(filePath, factsSymbol);
}

if (!factsText)
{
emitError(location) << "shared library '" << filePath << "' exports " << factsSymbol
<< " but it could not be read from the file";
return mlir::failure();
}

addImportedOwnNoDrops(*factsText);
}

// only now: an import that failed is tried again on the next pass, and must fail again
// rather than find itself already done
emittedFiles.insert(canonicalPath);
Expand Down
Loading
Loading