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
63 changes: 62 additions & 1 deletion tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@

#include "TypeScript/MLIRLogic/MLIRGenContextDefines.h"

// own_facts::isConstantData: -mm=own builds a tuple of values that own blocks in a slot
#include "OwnershipFacts.h"

#include <algorithm>
#include <iterator>
#include <type_traits>
Expand Down Expand Up @@ -10722,8 +10725,66 @@ class MLIRGenImpl
arrayValues.push_back(value);
}

// -mm=own: a tuple of values that own blocks (`[mk(1), mk(2)]` as `[N, N]`) is built as an object
// literal is, in a slot each value is stored into, which own reads as the literal taking them
// (spec 26). Made at once by `ts.CreateTuple`, each value had a second reference own could not
// place: the tuple's, and the temporary's own release.
// A tuple of data nothing owns (literals, `[[1, "x"], 2]`), or of reads (`[items[1]]`), stays one value.
auto isRead = [](mlir::Value value) {
while (auto *def = value.getDefiningOp())
{
if (!mlir::isa<mlir_ts::CastOp>(def) && !own_facts::isView(def))
{
break;
}

value = def->getOperand(0);
}

return !!value.getDefiningOp<mlir_ts::LoadOp>();
};
if (compileOptions.memoryModel == MemoryModelOwn &&
llvm::any_of(arrayValues, [&](mlir::Value value) { return !own_facts::isConstantData(value) && !isRead(value); }))
{
return createTupleInSlot(location, fieldInfos, arrayValues, genContext);
}

return V(builder.create<mlir_ts::CreateTupleOp>(location, getTupleType(fieldInfos), arrayValues));
}
}

// A tuple built in a slot, as mlirGenCreateTuple builds an object literal: a constant seed holds each
// constant value, and every other is stored into its field.
ValueOrLogicalResult createTupleInSlot(mlir::Location location, SmallVector<mlir_ts::FieldInfo> &fieldInfos,
ArrayRef<mlir::Value> values, const GenContext &genContext)
{
SmallVector<mlir::Attribute> seed;
for (auto value : values)
{
auto constOp = value.getDefiningOp<mlir_ts::ConstantOp>();
seed.push_back(constOp ? constOp.getValueAttr() : mlir::Attribute(builder.getUnitAttr()));
}

auto tupleType = getTupleType(fieldInfos);
auto seedValue = builder.create<mlir_ts::ConstantOp>(location, getConstTupleType(fieldInfos),
mlir::ArrayAttr::get(builder.getContext(), seed));
auto tupleVar = builder.create<mlir_ts::VariableOp>(location, mlir_ts::RefType::get(tupleType), seedValue,
builder.getBoolAttr(false), builder.getIndexAttr(0));
for (auto [index, value] : llvm::enumerate(values))
{
if (value.getDefiningOp<mlir_ts::ConstantOp>())
{
continue;
}

auto fieldRef = builder.create<mlir_ts::PropertyRefOp>(
location, mlir_ts::RefType::get(fieldInfos[index].type), tupleVar, builder.getI32IntegerAttr(index));
auto field = builder.create<mlir_ts::LoadOp>(location, fieldInfos[index].type, fieldRef);
auto saved = mlirGenSaveLogicOneItem(location, field, value, genContext);
EXIT_IF_FAILED(saved)
}

return V(builder.create<mlir_ts::LoadOp>(location, tupleType, tupleVar));
}

ValueOrLogicalResult createFixedSizeArrayFromArrayLiteral(mlir::Location location, ArrayRef<ArrayElement> values, struct ArrayInfo arrayInfo, const GenContext &genContext)
{
Expand Down
51 changes: 50 additions & 1 deletion tslang/lib/TypeScript/OwnershipInferenceMoves.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,17 @@ namespace own_inference
}
});

// a merge that only borrows it (movesOf) takes nothing
// a merge that only borrows it (movesOf) takes nothing; a field taken out of a record (`let [a, b] =
// pair()`) is taken by what takes the field
llvm::SmallVector<mlir::Operation *> takers;
for (auto [user, used] : notReads)
{
if (mlir::isa<mlir_ts::ExtractPropertyOp>(user))
{
fieldTakersOf(user, takers);
continue;
}

llvm::SmallVector<BorrowUse> uses;
if (!borrowsThroughMerge(user, used, value.getDefiningOp(), releases, uses, toErase))
{
Expand Down Expand Up @@ -686,6 +693,42 @@ namespace own_inference
return true;
}

bool OwnershipInferencePass::fieldOnlyRead(mlir::Operation *extract, llvm::SmallVectorImpl<mlir::Operation *> &reads)
{
auto onlyRead = true;
forEachUse(extract->getResult(0), [&](mlir::Operation *user, mlir::Value used) {
if (mlir::isa<mlir_ts::ExtractPropertyOp>(user))
{
onlyRead = fieldOnlyRead(user, reads) && onlyRead;
return;
}

if (!isBorrow(user, used))
{
onlyRead = false;
return;
}

reads.push_back(user);
});

return onlyRead;
}

void OwnershipInferencePass::fieldTakersOf(mlir::Operation *extract, llvm::SmallVectorImpl<mlir::Operation *> &takers)
{
forEachUse(extract->getResult(0), [&](mlir::Operation *user, mlir::Value used) {
if (mlir::isa<mlir_ts::ExtractPropertyOp>(user))
{
fieldTakersOf(user, takers);
}
else if (!mlir::isa<mlir_ts::RetainOp>(user) && !isBorrow(user, used))
{
takers.push_back(user);
}
});
}

bool OwnershipInferencePass::movesOf(mlir::Value value, llvm::SetVector<mlir::Operation *> &toErase)
{
llvm::SmallVector<mlir::Operation *> takers;
Expand All @@ -699,6 +742,7 @@ namespace own_inference
return;
}

llvm::SmallVector<mlir::Operation *> fieldReads;
if (mlir::isa<mlir_ts::ReleaseOp>(user))
{
releases.push_back(user);
Expand All @@ -708,6 +752,11 @@ namespace own_inference
reads.push_back(user);
readOf.try_emplace(user, used);
}
else if (mlir::isa<mlir_ts::ExtractPropertyOp>(user) && fieldOnlyRead(user, fieldReads))
{
reads.push_back(user);
reads.append(fieldReads.begin(), fieldReads.end());
}
else if (!mergedValuesOf(user, used).empty())
{
merges.push_back({user, used});
Expand Down
5 changes: 5 additions & 0 deletions tslang/lib/TypeScript/OwnershipInferencePass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,11 @@ namespace own_inference
continue; // a `let` that borrows a merge: borrowsThroughMerge erased it
}

if (toErase.contains(op))
{
continue; // decided already: a `let` that borrows a field of a record (checkValueMoves)
}

// a retain of what holds no block takes nothing: a union's number payload made into
// another union is a view of the union it came from, but a number all the way
if (auto retainOp = mlir::dyn_cast<mlir_ts::RetainOp>(op); retainOp && holdsNoBlock(retainOp.getReference()))
Expand Down
9 changes: 9 additions & 0 deletions tslang/lib/TypeScript/OwnershipInferencePass.h
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,15 @@ class OwnershipInferencePass
// The move verdict for an SSA value (phase 1). Reports and returns false when it is not one.
bool movesOf(mlir::Value value, llvm::SetVector<mlir::Operation *> &toErase);

// A field taken out of a record value (`ts.ExtractProperty`, `const r = rec(); r.a.x`) only to be
// read, through any depth of fields: a read of the record, as a field read through a slot is.
// Collects what reads it, each a read of the record that can come after a move of it.
bool fieldOnlyRead(mlir::Operation *extract, llvm::SmallVectorImpl<mlir::Operation *> &reads);

// What takes a field taken out of a record value, through any depth of fields: each use that is not a
// read. checkValueMoves lets them borrow it when each is a `let` its record's release bounds.
void fieldTakersOf(mlir::Operation *extract, llvm::SmallVectorImpl<mlir::Operation *> &takers);

// What a merge of branches makes of `used` (`c ? h : h2`): the result of an `arith.select` it
// is a choice of, or each block argument a branch passes it as. Empty for any other use.
static llvm::SmallVector<mlir::Value, 2> mergedValuesOf(mlir::Operation *user, mlir::Value used);
Expand Down
17 changes: 15 additions & 2 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3603,7 +3603,8 @@ foreach(own_test own_fresh_string own_fresh_array own_return_new own_try_local o
own_field_chain_let own_read_after_move own_conditional_move
own_interface_facts own_declared_scalar_no_drops
own_borrowing_record own_borrowing_record_arg
own_reduce own_fresh_call_result own_ternary_outer_loop own_destructure_elements)
own_reduce own_fresh_call_result own_ternary_outer_loop own_destructure_elements
own_tuple_record_values)
tslang_add_test(NAME test-jit-own-${own_test} COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/own/${own_test}.ts")
tslang_add_test(NAME test-compile-own-${own_test} COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/own/${own_test}.ts")
# The promise: no counting at all in what own emits.
Expand Down Expand Up @@ -3706,6 +3707,14 @@ endforeach()
tslang_add_test(NAME test-jit-own-00-owned-locals COMMAND test-runner -jit -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_locals.ts")
tslang_add_test(NAME test-compile-own-00-owned-locals COMMAND test-runner -mm=own "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_locals.ts")

# A tuple of owned values built or returned as a value, and a record a call returned read through a
# `const` or destructured: own builds the tuple in a slot as an object literal, and a field read out of
# the record is a read of it. The same program under every other model.
foreach(tuple_record_mm rc gc none)
tslang_add_test(NAME test-jit-${tuple_record_mm}-own_tuple_record_values COMMAND test-runner -jit -mm=${tuple_record_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_tuple_record_values.ts")
tslang_add_test(NAME test-compile-${tuple_record_mm}-own_tuple_record_values COMMAND test-runner -mm=${tuple_record_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_tuple_record_values.ts")
endforeach()

# A call through an interface closed in its module gets its implementations' facts (spec 31); the
# same program under every other model.
foreach(interface_facts_mm rc gc none)
Expand Down Expand Up @@ -4228,7 +4237,11 @@ set(own_error_cases
"own_err_borrowing_record_open|:17:31: error: '[^']*' borrows '[^']*' and cannot be stored, returned or captured"
"own_err_borrowing_record_borrowed_again|:29:11: error: '[^']*' borrows the result of 'A[.]hit' but is used here after it may be released or overwritten"
"own_err_destructure_element_reassigned|:10:11: error: '[^']*' borrows an element but is used here after it may be released or overwritten"
"own_err_destructure_element_returned|:9:5: error: '[^']*' borrows an element and cannot be stored, returned or captured")
"own_err_destructure_element_returned|:9:5: error: '[^']*' borrows an element and cannot be stored, returned or captured"
"own_err_record_field_returned|:12:5: error: '[^']*' borrows a field and cannot be stored, returned or captured"
"own_err_destructured_field_returned|:16:5: error: '[^']*' borrows '[^']*' and cannot be stored, returned or captured"
"own_err_record_field_after_move|:24:11: error: '[^']*' borrows a field but is used here after it may be released or overwritten"
"own_err_borrowing_record_const_field|:20:11: error: '[^']*' borrows the result of 'hit' but is used here after it may be released or overwritten")
foreach(own_error_case ${own_error_cases})
string(FIND "${own_error_case}" "|" own_error_sep)
string(SUBSTRING "${own_error_case}" 0 ${own_error_sep} own_error_name)
Expand Down
21 changes: 21 additions & 0 deletions tslang/test/tester/own/own_err_borrowing_record_const_field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// A record a call returns that borrows its argument (spec 32), held in a `const`: a field read out of
// it reads the borrowed argument, so it cannot come after the argument is overwritten.
class Ray {
constructor(public x: number) {}
}

interface Hit {
ray: Ray;
t: number;
}

function hit(r: Ray): Hit {
return { ray: r, t: 1 };
}

function main() {
let r = new Ray(1);
const h = hit(r);
r = new Ray(2);
print(h.ray.x);
}
22 changes: 22 additions & 0 deletions tslang/test/tester/own/own_err_destructured_field_returned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// `let [a] = pair()` borrows the returned tuple's first field, which the end of the function releases.
class N {
constructor(public x: number) {}
}

function mk(i: number) {
return new N(i);
}

function pair(): [N, N] {
return [mk(1), mk(2)];
}

function first() {
let [a] = pair();
return a;
}

function main() {
print(first().x);
}

25 changes: 25 additions & 0 deletions tslang/test/tester/own/own_err_record_field_after_move.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// A field read out of a record is a read of the record: given away to a callee that keeps it, the record
// is gone, and the field read after that reads freed memory.
class N {
constructor(public x: number) {}
}

function mk(i: number) {
return new N(i);
}

class K {
recs: { a: N; b: N }[] = [];
}

function keep(k: K, r: { a: N; b: N }) {
k.recs.push(r);
}

function main() {
const k = new K();
const r = { a: mk(1), b: mk(2) };
const a = r.a;
keep(k, r);
print(a.x);
}
18 changes: 18 additions & 0 deletions tslang/test/tester/own/own_err_record_field_returned.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// A field of a record the function made is the record's: returning it alone would outlive the record.
class N {
constructor(public x: number) {}
}

function mk(i: number) {
return new N(i);
}

function first() {
const r = { a: mk(1), b: mk(2) };
return r.a;
}

function main() {
print(first().x);
}

76 changes: 76 additions & 0 deletions tslang/test/tester/own/own_tuple_record_values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// A tuple or record of owned values as a value: an array literal typed as a tuple of objects
// (`[mk(1), mk(2)]` as `[N, N]`), built in a local or returned, and a returned record read through a
// `const` or destructured into `let`s. Under own the tuple literal took a second reference of each
// value (`ts.CreateTuple` beside the temporaries' releases), and a field taken out of a `const` record
// moved it, so a second read was a use after the move. Each read follows allocations of the same
// size, so a freed block would be reused.
class N {
tag = "";
constructor(public x: number) {
this.tag = "n" + x;
}
}

function churn() {
const keep: N[] = [];
for (let i = 0; i < 1000; i++) keep.push(new N(777));
return keep.length;
}

function mk(i: number) {
return new N(i);
}

function pair(): [N, N] {
return [mk(1), mk(2)];
}

function mixed(): [N, number] {
return [mk(3), 4];
}

function rec() {
return { a: mk(5), b: mk(6) };
}

function check(n: N, x: number, what: string) {
assert(n.x == x && n.tag == "n" + x, what);
}

function main() {
const local: [N, N] = [mk(7), mk(8)];
churn();
check(local[0], 7, "a local tuple's first");
check(local[1], 8, "a local tuple's second");

const p = pair();
churn();
check(p[0], 1, "a returned tuple's first");
check(p[1], 2, "a returned tuple's second");

const m = mixed();
churn();
check(m[0], 3, "a mixed tuple's object");
assert(m[1] == 4, "a mixed tuple's number");

const r = rec();
churn();
check(r.a, 5, "a const record's first field");
check(r.b, 6, "a const record's second field");
check(r.a, 5, "a const record's first field, read again");

let [a, b] = pair();
churn();
check(a, 1, "destructured first");
check(b, 2, "destructured second");

let sum = 0;
for (let i = 0; i < 3; i++) {
const q = pair();
churn();
sum += q[0].x + q[1].x;
}

assert(sum == 9, "a tuple per iteration");
print("done.");
}
Loading