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
34 changes: 34 additions & 0 deletions TSLANG_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,41 @@ given a new array (`let dir = this.dir; ... dir = rotateAround(...)`). Regressio
`tslang/test/tester/tests/00tslang_issue_55.ts` (every model, and as an exe) and
`own/own_err_borrowing_array_field_reset.ts`.

56 (a `let` given only fields: `let layout = this.computeLayout; ... if (this.fp16Enabled) { layout =
this.computeLayoutFP16; ... }` in `examples/nri/16bit_arithmetic_nri.ts`, refused "borrows a field and
cannot be assigned; -mm=own cannot prove that yet") is fixed. An owning `let` whose declaration and
every assignment give it a read of a field or an element, or a parameter, borrows each value it is
given until the next assignment. Each read's uses are the `let`'s reads its own store reaches before
another assignment, and the assignments end it, so a field written once the `let` holds another field
is fine, in a loop too. rc's references for the `let` (its retain, its releases, and the retain for each
assignment) go. Still refused: the field the `let` holds written (or a call that may write it) before a
use, the `let` stored or returned, and a fresh value assigned (`dir = rotateAround(...)` in
`core/donut_first_person_camera.ts`, which would make the `let` own its value on some ways and borrow
it on others). One shape is refused that is sound: in a loop that reads the `let`, assigns it another
field and then writes the first, a read after the loop is taken to see the first field. In the sweep
the only `cannot be assigned` errors left are the camera's two. Regression tests
`tslang/test/tester/tests/00tslang_issue_56.ts` (every model, and as an exe) and
`own/own_err_let_borrows_reassigned_overwritten.ts`.

57 (a `let` that only holds parameters: `let src = order; let dst = scratch; ... const swap = src; src =
dst; dst = swap;` in `core/octomap_common.ts`'s exported `sortDescending`, refused "a parameter, kept
here, but -mm=own cannot make the callers of 'sortDescending' give it up") is fixed. Even `let s = a;
return s.length;` was refused: MLIRGen makes every `let` an owning local, so storing a parameter into
one kept the parameter. A `let` whose declaration and every assignment give it a parameter (read out of
a slot nothing assigns) or the value of another such `let` holds the caller's values for the whole
call, so before the facts the signature pass makes it a plain local that owns nothing and drops rc's
counting for it. What is done with its reads is then what is done with the parameter: stored into a
field or a global, captured, or given a fresh value (`s = [9]`) it is still refused. A `Shared<T>`
handle (counted under own) and a string (copied) are left as they were. `octomap_common.ts` compiles under
own now, and with it `examples/render_octomap.ts`. Six of ntg's `core/` modules are left that do not.
Regression tests `tslang/test/tester/tests/00tslang_issue_57.ts` (every model, and as an exe) and
`own/own_err_parameter_let_stored.ts`.

For native_ts_graphics, from the same sweep (its code, not the compiler):
- `core/ray_tracing_extended_scene.ts`: the exported classes' constructors keep their array parameters
(`this.origin = origin`), which an exported constructor has to say: `@keeps("origin", "direction")`
on `FlameParticleGenerator`'s and `@keeps("vertices", "triangles")` on `Model`'s. `@keeps` on a
constructor works (checked). Its `this.particles.filter(...)` needs `Shared<FlameParticle>` elements.
- `examples/dynamic_primitive_clipping.ts:223`, `desc.setDepthClip(clip)` with `clip` the loop's `int`,
fails under every model since #846 (a number is not a `boolean` argument, TS2345). alpha127 still
takes it; the next release won't. `desc.setDepthClip(clip != 0)` fixes it.
Expand Down
99 changes: 97 additions & 2 deletions tslang/lib/TypeScript/OwnershipInferenceBorrowers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,12 +249,107 @@ namespace own_inference
return true;
}

mlir::Operation *OwnershipInferencePass::collectBorrowerUses(mlir_ts::VariableOp borrower, llvm::ArrayRef<mlir::Operation *> kills,
bool OwnershipInferencePass::borrowsOnly(mlir_ts::VariableOp varOp)
{
// `let layout = this.computeLayout; if (fp16) layout = this.computeLayoutFP16;` (TSLANG 56):
// an owning `let` given only reads of places and parameters, each a borrow of its own
auto borrowed = [&](mlir::Value value) {
auto root = rootOf(value);
auto rootLoad = root.getDefiningOp<mlir_ts::LoadOp>();
auto slot = rootLoad ? rootLoad.getReference().getDefiningOp<mlir_ts::VariableOp>() : mlir_ts::VariableOp();
int index = -1;
return placeReadOf(root) || (slot && isParameterSlot(slot, index));
};

if (!varOp.getInitializer() || !isOwningVariable(varOp) || varOp.getCaptured().value_or(false) ||
!borrowed(varOp.getInitializer()))
{
return false;
}

auto assigned = false;
for (auto *user : varOp.getResult().getUsers())
{
if (isAssignmentOf(user, varOp))
{
if (!borrowed(mlir::cast<mlir_ts::StoreOp>(user).getValue()))
{
return false;
}

assigned = true;
}
}

return assigned;
}

mlir_ts::VariableOp OwnershipInferencePass::borrowingAssignmentOf(mlir::Operation *user, mlir::Value value)
{
// the assignment itself
if (auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(user); storeOp && storeOp.getValue() == value)
{
auto varOp = storeOp.getReference().getDefiningOp<mlir_ts::VariableOp>();
return varOp && borrowsOnly(varOp) ? varOp : mlir_ts::VariableOp();
}

// rc's reference for it, right before it
if (mlir::isa<mlir_ts::RetainOp>(user) && user->getOperand(0) == value)
{
for (auto *next = user->getNextNode(); next; next = next->getNextNode())
{
if (auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(next); storeOp && storeOp.getValue() == value)
{
auto varOp = storeOp.getReference().getDefiningOp<mlir_ts::VariableOp>();
return varOp && borrowsOnly(varOp) ? varOp : mlir_ts::VariableOp();
}

if (!mlir::isa<mlir_ts::ReleaseSlotOp>(next))
{
break;
}
}
}

return {};
}

mlir::Operation *OwnershipInferencePass::collectBorrowerUses(mlir_ts::VariableOp borrower, llvm::ArrayRef<mlir::Operation *> givenKills,
llvm::SmallVectorImpl<BorrowUse> &uses,
llvm::SmallVectorImpl<mlir::Operation *> &bookkeeping)
llvm::SmallVectorImpl<mlir::Operation *> &bookkeeping, mlir::Operation *from)
{
// a `let` given only borrows (TSLANG 56): each borrow lasts until the next assignment, and its
// reads are those its own store (`from`: the declaration or an assignment) reaches before one
llvm::SmallVector<mlir::Operation *, 4> kills(givenKills.begin(), givenKills.end());
llvm::SmallVector<mlir::Operation *, 4> assignments;
auto onlyBorrows = borrowsOnly(borrower);
if (onlyBorrows)
{
for (auto *user : borrower.getResult().getUsers())
{
if (isAssignmentOf(user, borrower))
{
kills.push_back(user);
if (user != from)
{
assignments.push_back(user);
}
}
}
}

for (auto *user : borrower.getResult().getUsers())
{
if (onlyBorrows && isAssignmentOf(user, borrower))
{
continue;
}

if (onlyBorrows && from && mlir::isa<mlir_ts::LoadOp>(user) && !reachableAfter(from, user, assignments))
{
continue;
}

if (mlir::isa<mlir_ts::RetainSlotOp, mlir_ts::ReleaseSlotOp>(user))
{
bookkeeping.push_back(user);
Expand Down
12 changes: 10 additions & 2 deletions tslang/lib/TypeScript/OwnershipInferencePass.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ class OwnershipInferencePass
// An array literal held in a local, made only of reads of places and parameters and only read
// (TSLANG 55): it borrows its elements while it is used, and its releases are shallow.
void classifyLocalBorrowingArrays();
// An owning `let` given only reads of places and parameters, and assigned at least once
// (TSLANG 56): each value it is given is a borrow until the next assignment.
bool borrowsOnly(mlir_ts::VariableOp varOp);
// The `let` (borrowsOnly) that `user` assigns `value` to - the store itself, or rc's retain right
// before it - or null.
mlir_ts::VariableOp borrowingAssignmentOf(mlir::Operation *user, mlir::Value value);

// Is `ref` a cell read out of the `.captured` box of a generator's state object that borrows:
// `ts.Load(ts.PropertyRef(ts.Load(ts.PropertyRef(state, captured)), i))`?
Expand Down Expand Up @@ -517,10 +523,12 @@ class OwnershipInferencePass
// A borrowing `let`'s slot: each read, and everything it leads to (walkBorrowed), joins
// `uses`, made again at `kills`; its `ts.RetainSlot` and `ts.ReleaseSlot`s are `bookkeeping`,
// which a borrow erases. Answers the first use that would keep what the slot holds - an
// assignment, or anything else that is not a read - or null.
// assignment, or anything else that is not a read - or null. For a `let` given only borrows
// (borrowsOnly, TSLANG 56) its assignments end each borrow, and with `from` (the store of the
// value: the declaration or an assignment) only the reads it reaches before one are its uses.
mlir::Operation *collectBorrowerUses(mlir_ts::VariableOp borrower, llvm::ArrayRef<mlir::Operation *> kills,
llvm::SmallVectorImpl<BorrowUse> &uses,
llvm::SmallVectorImpl<mlir::Operation *> &bookkeeping);
llvm::SmallVectorImpl<mlir::Operation *> &bookkeeping, mlir::Operation *from = nullptr);

static bool isAssignmentOf(mlir::Operation *op, mlir_ts::VariableOp varOp);

Expand Down
22 changes: 20 additions & 2 deletions tslang/lib/TypeScript/OwnershipInferencePlaceReads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -833,11 +833,29 @@ namespace own_inference
auto borrower = borrowerOf(user, /*consumed=*/true);
if (!borrower || borrower.getOperation() != user)
{
return false;
// assigned to a `let` given only borrows (TSLANG 56): the `let` borrows it from the
// assignment on, and rc's retain for the assignment goes
auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(user);
auto operand = storeOp ? storeOp.getValue()
: (mlir::isa<mlir_ts::RetainOp>(user) ? user->getOperand(0) : mlir::Value());
auto assigned = operand && rootOf(operand) == rootOf(result) ? borrowingAssignmentOf(user, operand)
: mlir_ts::VariableOp();
if (!assigned)
{
return false;
}

if (!storeOp)
{
bookkeeping.push_back(user);
return true;
}

borrower = assigned;
}

name = varName(borrower);
if (auto *keeps = collectBorrowerUses(borrower, kills, uses, bookkeeping); keeps && !letKept)
if (auto *keeps = collectBorrowerUses(borrower, kills, uses, bookkeeping, user); keeps && !letKept)
{
letKept = keeps;
letKeeps = borrower;
Expand Down
111 changes: 111 additions & 0 deletions tslang/lib/TypeScript/OwnershipSignatureFieldLocals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,117 @@ namespace own_signature
};
} // namespace

void OwnershipSignaturePass::borrowParametersInLocals(mlir::ModuleOp module)
{
// `let src = order; let dst = scratch; ... const swap = src; src = dst; dst = swap;` (TSLANG
// 57, native_ts_graphics' sortDescending): a `let` that only ever holds the function's
// parameters - read out of their slots, or out of another such `let` - holds the caller's
// values for the whole call. MLIRGen made it an owning local, so storing a parameter into it
// kept the parameter. It owns nothing: it is made a plain local, its rc counting goes, and what
// is done with its reads is what is done with the parameter.
module.walk([&](mlir::FunctionOpInterface funcOp) {
if (funcOp.isExternal())
{
return;
}

auto &body = funcOp.getFunctionBody();
llvm::SetVector<mlir_ts::VariableOp> aliases;
body.walk([&](mlir_ts::VariableOp varOp) {
// not a Shared<T> handle, which own counts (spec 23.2), nor a string and the like,
// which a local takes a copy of
auto type = varOp.getInitializer() ? varOp.getInitializer().getType() : mlir::Type();
if (varOp->hasAttr(OWNED_LOCAL_ATTR_NAME) && !varOp->hasAttr(OWNED_LOCAL_CONSUMED_ATTR_NAME) &&
type && !varOp.getCaptured().value_or(false) && !MLIRTypeHelper::isSharedHandleType(type) &&
!own_facts::isCopyableValueAt(varOp, type))
{
aliases.insert(varOp);
}
});

// a parameter's slot nothing assigns, or a `let` still in the set
auto aliased = [&](mlir::Value value) {
auto loadOp = own_facts::rootOf(value).getDefiningOp<mlir_ts::LoadOp>();
auto slot = loadOp ? loadOp.getReference().getDefiningOp<mlir_ts::VariableOp>() : mlir_ts::VariableOp();
int index = -1;
return slot && (aliases.contains(slot) || (own_facts::isParameterSlot(slot, index) && isUnassignedSlot(slot.getResult())));
};

auto holdsOnlyParameters = [&](mlir_ts::VariableOp varOp) {
if (!aliased(varOp.getInitializer()))
{
return false;
}

for (auto *user : varOp->getUsers())
{
if (mlir::isa<mlir_ts::LoadOp, mlir_ts::RetainSlotOp, mlir_ts::ReleaseSlotOp, mlir_ts::DebugVariableOp>(user))
{
continue;
}

auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(user);
if (!storeOp || storeOp.getReference() != varOp.getResult() || !aliased(storeOp.getValue()))
{
return false;
}
}

return true;
};

// to a fixed point: a `let` that takes another's value stays only while that one does
for (auto changed = true; changed;)
{
changed = false;
for (auto varOp : llvm::to_vector(aliases))
{
if (!holdsOnlyParameters(varOp))
{
aliases.remove(varOp);
changed = true;
}
}
}

for (auto varOp : aliases)
{
varOp->removeAttr(OWNED_LOCAL_ATTR_NAME);
llvm::SmallVector<mlir::Operation *> counting;
for (auto *user : varOp->getUsers())
{
if (mlir::isa<mlir_ts::RetainSlotOp, mlir_ts::ReleaseSlotOp>(user))
{
counting.push_back(user);
}

// rc's retain for an assignment, right before it (`Retain(v); ReleaseSlot(x); Store(v, x)`)
if (auto storeOp = mlir::dyn_cast<mlir_ts::StoreOp>(user))
{
for (auto *before = user->getPrevNode(); before; before = before->getPrevNode())
{
if (auto retainOp = mlir::dyn_cast<mlir_ts::RetainOp>(before); retainOp && retainOp->getOperand(0) == storeOp.getValue())
{
counting.push_back(before);
break;
}

if (!mlir::isa<mlir_ts::ReleaseSlotOp>(before))
{
break;
}
}
}
}

for (auto *op : counting)
{
op->erase();
}
}
});
}

void OwnershipSignaturePass::readFieldsForLocals(mlir::ModuleOp module)
{
llvm::SmallVector<mlir::Region *> bodies;
Expand Down
1 change: 1 addition & 0 deletions tslang/lib/TypeScript/OwnershipSignaturePass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ namespace own_signature
{
auto module = getOperation();
stubCannotShare(module);
borrowParametersInLocals(module);
readFieldsForLocals(module);
module.walk([&](mlir_ts::FuncOp funcOp) { functions[funcOp.getSymName()] = funcOp; });
module.walk([&](mlir_ts::GlobalOp globalOp) { globals[globalOp.getSymName()] = globalOp; });
Expand Down
4 changes: 4 additions & 0 deletions tslang/lib/TypeScript/OwnershipSignaturePass.h
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,10 @@ class OwnershipSignaturePass : public mlir::PassWrapper<OwnershipSignaturePass,
// in every function of the module.
void readFieldsForLocals(mlir::ModuleOp module);

// A `let` that only ever holds the function's parameters (TSLANG 57): `let src = order; ...
// src = dst;`. It holds the caller's values, so it is made a plain local that owns nothing.
void borrowParametersInLocals(mlir::ModuleOp module);

// ---- Drops ----

// Least fixpoint: a function drops nothing until its body or a call it makes shows it may.
Expand Down
Loading
Loading