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 @@ -3887,9 +3887,26 @@ and the same errors as a call's result that borrows its arguments.)
parameter's, `this`'s, or a read out of one: those parameters are the set. A record on one path
and anything else on another (a plain borrow, a fresh object) gives no facts. `closest` returns
`bestHit`, which holds `sc.ts[i].hit(ray)`'s records: its result borrows `ray` and `sc`.
`@lends` may name several parameters: `@lends("a", "b") pairOf(a, b): Pair { return { a: a, b: b }
}` (`own_lends`). A declaration's `@lends`, seen from another module without its body, is a plain
borrow: a record's box then leaks at that caller, and nothing is freed twice.
`@lends` may name several parameters: `@lends("a", "b") @record pairOf(a, b): Pair { return { a: a,
b: b } }` (`own_lends`).
- **`@record` (USER decision 2026-10-23).** A declaration seen from another module has no body to
say whether the result is a record, so `@record` beside `@lends` says it
(`OWN_DECLARED_RECORD_ATTR_NAME`, a unit attribute). The declaration printer emits it after
`@lends`; the importer's declaration gets it, and its calls get `__own_result_record`
(`meetFacts`, and `calleesDeclare` for a call no function resolves). Under own the importer's
OwnedReturnConsumptionPass counts such a call's result owned (`calleeDeclaresRecord`: by name,
through a dynamic import's global, or as a method), so the box is released where the caller
lets it go. Before, the importer took the result for a plain borrow and the box leaked; nothing
was freed twice. Guessing record-ness from the result type, or from `@lends` naming several
parameters, is not safe: a plain borrow of an interface argument looks the same, and a caller
told it owns a record would free that argument. The defining module checks the marker against
its body (`checkDeclared`): `@record` without `@lends` is an error, `@record` on a body that
returns a borrow as it is is an error, and a function another module can call (exported, in a
module that is not an executable) whose body returns a record has to say `@record`. A function
no other module calls need not: its callers go by its body. An override says the same
`@record` as what it overrides. Tests: `export_own_record` / `import_own_record` (-shared, every
model), `import_own_record_outlives`, `own-shared-record.cmake` (the importer releases each box),
`own_err_record_without_lends`, `own_err_record_not_record`, `own_err_record_exported_unsaid`.
- **Families** meet by agreement, as before (`meetFacts`): members whose sets or record-ness differ
are demoted. The union of the sets would be sound for records (the caller would bound the result by
more arguments), but not across a record and a plain borrow, whose callers release different
Expand Down
6 changes: 6 additions & 0 deletions tslang/include/TypeScript/Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@
#define OWN_DECLARED_KEEPS_ATTR_NAME "ts.own_declared_keeps"
#define OWN_DECLARED_BORROWS_ATTR_NAME "ts.own_declared_borrows"
#define OWN_DECLARED_LENDS_ATTR_NAME "ts.own_declared_lends"
// `@record`, beside `@lends`: the result is a borrowing record (own spec 32) - a box the caller owns,
// holding a borrow of each argument `@lends` names - not a borrow of one of them. An importer, which
// sees the declaration without its body, learns it only from this; a function another module can call
// that returns such a record has to say it. A unit attribute on the FuncOp.
#define RECORD_DECORATOR "record"
#define OWN_DECLARED_RECORD_ATTR_NAME "ts.own_declared_record"
// the names of the arguments of a function that says any, for -mm=own's errors about it
#define OWN_DECLARED_PARAM_NAMES_ATTR_NAME "ts.own_declared_param_names"

Expand Down
5 changes: 3 additions & 2 deletions tslang/include/TypeScript/MLIRLogic/MLIRDeclarationPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ namespace typescript
void printEnum(StringRef, NamespaceInfo::TypePtr, mlir::DictionaryAttr);
void printVariableDeclaration(StringRef, NamespaceInfo::TypePtr, mlir::Type, bool, StringRef = StringRef());
void print(StringRef, NamespaceInfo::TypePtr, mlir_ts::FunctionType, StringRef = StringRef(),
ArrayRef<int32_t> ownKeeps = {}, ArrayRef<int32_t> ownBorrows = {}, ArrayRef<int32_t> ownLends = {});
ArrayRef<int32_t> ownKeeps = {}, ArrayRef<int32_t> ownBorrows = {}, ArrayRef<int32_t> ownLends = {},
bool ownRecord = false);
void print(ClassInfo::TypePtr);
void print(InterfaceInfo::TypePtr);
void printGenericClass(NamespaceInfo::TypePtr, StringRef);
Expand All @@ -40,7 +41,7 @@ namespace typescript
bool filterName(StringRef);
bool filterField(mlir::Attribute);
void printParams(ArrayRef<mlir::Type>, mlir::Type);
void printOwnMarkers(ArrayRef<int32_t>, ArrayRef<int32_t>, ArrayRef<int32_t>, bool, unsigned);
void printOwnMarkers(ArrayRef<int32_t>, ArrayRef<int32_t>, ArrayRef<int32_t>, bool, bool, unsigned);
void printFunction(StringRef, ArrayRef<mlir::Type>, mlir::Type);
void printMethod(bool, StringRef, ArrayRef<mlir::Type>, mlir::Type, mlir::Type);
void printAccessor(bool, StringRef, StringRef, mlir_ts::AccessLevel, ArrayRef<mlir::Type>, mlir::Type, mlir::Type, StringRef);
Expand Down
2 changes: 2 additions & 0 deletions tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ struct MethodInfo
std::vector<int32_t> ownKeeps;
std::vector<int32_t> ownBorrows;
uint64_t ownLendsMask = 0;
// `@record` (OWN_DECLARED_RECORD_ATTR_NAME): what `@lends` names, the result is a record of
bool ownRecord = false;
};

struct GenericMethodInfo
Expand Down
18 changes: 13 additions & 5 deletions tslang/lib/TypeScript/DeclarationPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,10 @@ namespace typescript
}

// `@keeps("p1")`, `@borrows("this", "p1")`: by the names printParams gives the parameters, `this`
// the receiver of a method, so the importer reads the same argument indices back
// the receiver of a method, so the importer reads the same argument indices back. `@record` after
// `@lends`: the importer's only way to know the result is a record it owns (own spec 32).
void MLIRDeclarationPrinter::printOwnMarkers(ArrayRef<int32_t> keeps, ArrayRef<int32_t> borrows, ArrayRef<int32_t> lends,
bool hasThis, unsigned indent)
bool record, bool hasThis, unsigned indent)
{
auto printMarker = [&](StringRef name, ArrayRef<int32_t> indices) {
if (indices.empty())
Expand Down Expand Up @@ -263,6 +264,12 @@ namespace typescript
printMarker(KEEPS_DECORATOR, keeps);
printMarker(BORROWS_DECORATOR, borrows);
printMarker(LENDS_DECORATOR, lends);
if (record && !lends.empty())
{
os.indent(indent);
os << "@" RECORD_DECORATOR;
newline();
}
}

void MLIRDeclarationPrinter::printFunction(StringRef name, ArrayRef<mlir::Type> params, mlir::Type returnType)
Expand Down Expand Up @@ -440,7 +447,8 @@ namespace typescript
}

void MLIRDeclarationPrinter::print(StringRef name, NamespaceInfo::TypePtr elementNamespace, mlir_ts::FunctionType funcType, StringRef dllName,
ArrayRef<int32_t> ownKeeps, ArrayRef<int32_t> ownBorrows, ArrayRef<int32_t> ownLends)
ArrayRef<int32_t> ownKeeps, ArrayRef<int32_t> ownBorrows, ArrayRef<int32_t> ownLends,
bool ownRecord)
{
printNamespaceBegin(elementNamespace);

Expand All @@ -452,7 +460,7 @@ namespace typescript
newline();
}

printOwnMarkers(ownKeeps, ownBorrows, ownLends, /*hasThis=*/false, 0);
printOwnMarkers(ownKeeps, ownBorrows, ownLends, ownRecord, /*hasThis=*/false, 0);

printFunction(
name,
Expand Down Expand Up @@ -710,7 +718,7 @@ namespace typescript
}
}

printOwnMarkers(method.ownKeeps, method.ownBorrows, lends, /*hasThis=*/!method.isStatic, 4);
printOwnMarkers(method.ownKeeps, method.ownBorrows, lends, method.ownRecord, /*hasThis=*/!method.isStatic, 4);

// the slot of a @cpp class's virtual method: without it the importer takes the method
// for a non-virtual one
Expand Down
2 changes: 1 addition & 1 deletion tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2072,7 +2072,7 @@ struct FuncOpLowering : public TsLlvmPattern<mlir_ts::FuncOp>
// read by -mm=own only
if (name == OWN_DECLARED_NO_DROPS_ATTR_NAME || name == OWN_DECLARED_KEEPS_ATTR_NAME ||
name == OWN_DECLARED_BORROWS_ATTR_NAME || name == OWN_DECLARED_LENDS_ATTR_NAME ||
name == OWN_DECLARED_PARAM_NAMES_ATTR_NAME) {
name == OWN_DECLARED_RECORD_ATTR_NAME || name == OWN_DECLARED_PARAM_NAMES_ATTR_NAME) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion tslang/lib/TypeScript/MLIRGenFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,7 @@ namespace mlirgen
if (auto globalOp = theModule.lookupSymbol<mlir_ts::GlobalOp>(globalName))
{
for (auto *name : {OWN_DECLARED_KEEPS_ATTR_NAME, OWN_DECLARED_BORROWS_ATTR_NAME, OWN_DECLARED_LENDS_ATTR_NAME,
OWN_DECLARED_PARAM_NAMES_ATTR_NAME})
OWN_DECLARED_RECORD_ATTR_NAME, OWN_DECLARED_PARAM_NAMES_ATTR_NAME})
{
if (auto attr = declared->getAttr(name))
{
Expand Down
27 changes: 26 additions & 1 deletion tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3296,6 +3296,28 @@ class MLIRGenImpl
{
auto result = mlir::success();
iterateDecorators(functionLikeDeclarationBaseAST, genContext, [&](StringRef name, SmallVector<StringRef> args) {
// `@record` names nothing: it says what `@lends` lends into (OWN_DECLARED_RECORD_ATTR_NAME)
if (name == RECORD_DECORATOR)
{
if (llvm::any_of(attrs, [](mlir::NamedAttribute &attr) { return attr.getName() == OWN_DECLARED_RECORD_ATTR_NAME; }))
{
emitError(location) << "'@" RECORD_DECORATOR "' is given twice";
result = mlir::failure();
return;
}

if (!args.empty())
{
emitError(location) << "'@" RECORD_DECORATOR "' names no parameter: '@" LENDS_DECORATOR
"' names those the record borrows";
result = mlir::failure();
return;
}

attrs.push_back({builder.getStringAttr(OWN_DECLARED_RECORD_ATTR_NAME), builder.getUnitAttr()});
return;
}

if (name != KEEPS_DECORATOR && name != BORROWS_DECORATOR && name != LENDS_DECORATOR)
{
return;
Expand Down Expand Up @@ -12868,6 +12890,8 @@ class MLIRGenImpl
}
}
}

methodInfo.ownRecord = funcOp->hasAttr(OWN_DECLARED_RECORD_ATTR_NAME);
}

if (propertyName.size() > 0)
Expand Down Expand Up @@ -14243,7 +14267,8 @@ class MLIRGenImpl
return attr ? attr.asArrayRef() : ArrayRef<int32_t>();
};
dp.print(funcProto->getNameWithoutNamespace(), elementNamespace, funcProto->getFuncType(), dllName,
marker(OWN_DECLARED_KEEPS_ATTR_NAME), marker(OWN_DECLARED_BORROWS_ATTR_NAME), marker(OWN_DECLARED_LENDS_ATTR_NAME));
marker(OWN_DECLARED_KEEPS_ATTR_NAME), marker(OWN_DECLARED_BORROWS_ATTR_NAME), marker(OWN_DECLARED_LENDS_ATTR_NAME),
funcOp && funcOp->hasAttr(OWN_DECLARED_RECORD_ATTR_NAME));

declExports << ss.str().str();
}
Expand Down
59 changes: 57 additions & 2 deletions tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,23 @@ class OwnedReturnConsumptionPass
});
});

// -mm=own: a function another module defines, whose declaration says `@record`, returns a box
// its caller owns (own spec 32) - the one result of an import this pass can count as owned
auto own = compileOptions.memoryModel == MemoryModelOwn;
auto anyDeclaredRecord = false;
if (own)
{
module.walk([&](mlir::Operation *op) {
if (mlir::isa<mlir_ts::FuncOp, mlir_ts::GlobalOp>(op) && op->hasAttr(OWN_DECLARED_RECORD_ATTR_NAME))
{
anyDeclaredRecord = true;
}
});
}

// `new C()` is marked where it is built, so there can be discarded temporaries to give
// back even when no function here is classified as returning owned.
if (returnsOwned.empty())
if (returnsOwned.empty() && !anyDeclaredRecord)
{
claimGlobalInitializers(mth, module);
releaseDiscardedTemporaries(mth, module);
Expand All @@ -176,7 +190,8 @@ class OwnedReturnConsumptionPass
}

auto callee = calleeNameOf(callOp, fieldFunctions);
auto calleeReturnsOwned = !callee.empty() && returnsOwned.contains(callee);
auto calleeReturnsOwned = (!callee.empty() && returnsOwned.contains(callee)) ||
(anyDeclaredRecord && calleeDeclaresRecord(module, callOp));

// A call through a plain value - a callback handed to `reduce`, a function-typed
// field - names nothing at all, and neither vtables nor member names help. What does
Expand Down Expand Up @@ -2137,6 +2152,46 @@ class OwnedReturnConsumptionPass
// canonicalizer (SimplifyIndirectCallWithKnownCallee) already rewrites into direct calls,
// which is the argument that they name one callee: it is the same judgement, made here
// before canonicalization has run.
// Does the callee say `@record` (OWN_DECLARED_RECORD_ATTR_NAME)? By name, through the global a
// dynamically imported function is read from, or as a method - virtual or not: an override says
// the same markers as what it overrides (OwnershipSignaturePass::checkDeclared).
static bool calleeDeclaresRecord(mlir::ModuleOp module, mlir_ts::CallIndirectOp callOp)
{
if (callOp.getNumOperands() == 0)
{
return false;
}

auto callee = callOp.getOperand(0);
mlir::StringRef name;
if (auto symbolRefOp = callee.getDefiningOp<mlir_ts::SymbolRefOp>())
{
name = symbolRefOp.getIdentifier();
}
else if (auto loadOp = callee.getDefiningOp<mlir_ts::LoadOp>())
{
if (auto addressOfOp = loadOp.getReference().getDefiningOp<mlir_ts::AddressOfOp>())
{
name = addressOfOp.getGlobalName();
}
}
else if (auto getMethodOp = callee.getDefiningOp<mlir_ts::GetMethodOp>())
{
auto *boundOp = getMethodOp.getBoundFunc().getDefiningOp();
if (auto thisSymbolRefOp = mlir::dyn_cast_or_null<mlir_ts::ThisSymbolRefOp>(boundOp))
{
name = thisSymbolRefOp.getIdentifier();
}
else if (auto thisVirtualSymbolRefOp = mlir::dyn_cast_or_null<mlir_ts::ThisVirtualSymbolRefOp>(boundOp))
{
name = thisVirtualSymbolRefOp.getIdentifier();
}
}

auto *symbol = name.empty() ? nullptr : mlir::SymbolTable::lookupSymbolIn(module, name);
return symbol && symbol->hasAttr(OWN_DECLARED_RECORD_ATTR_NAME);
}

static mlir::StringRef calleeNameOf(mlir_ts::CallIndirectOp callOp, const own_facts::FieldFunctions *fieldFunctions)
{
if (callOp.getNumOperands() == 0)
Expand Down
Loading
Loading