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
42 changes: 31 additions & 11 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1476,9 +1476,16 @@ class MLIRGenImpl
}

// A record read out whole from the temporary slot an object literal is built in (one that
// owns nothing of its own), with a field given a value read out of another object's field
// or element, or out of an owning local: what `{ s: p.child }` and `{ x: a }` are. Such a
// value carries no reference, and the literal takes none for it.
// owns nothing of its own), with a field that owns a block and that only the literal's
// receiver counts - a `let` does, with RetainSlot - so a `const` of it needs storage like a
// `let`. Folded:
// - a value read out of another object's field or element, or out of an owning local
// (`{ s: p.child }`, `{ x: a }`), was held with no count at all (#454, #460);
// - an array or tuple built in the literal (`{ items: [i, 2] }`), or a new array, born with
// no count, was never given back, and under own neither was a string, a call's result or
// a new instance made for it (#485).
// A value taken out of another record (`{ ...o }`) stays with that record's count, and a
// literal string is immortal.
static bool readsLiteralSlot(MLIRTypeHelper &mth, mlir::Location location, mlir::Value value)
{
auto loadOp = value ? value.getDefiningOp<mlir_ts::LoadOp>() : mlir_ts::LoadOp();
Expand Down Expand Up @@ -1520,19 +1527,32 @@ class MLIRGenImpl
stored = castOp.getIn();
}

auto storedLoad = stored.getDefiningOp<mlir_ts::LoadOp>();
auto *place = storedLoad ? storedLoad.getReference().getDefiningOp() : nullptr;
if (place && isa<mlir_ts::PropertyRefOp, mlir_ts::ElementRefOp>(place))
auto *producer = stored.getDefiningOp();
if (!producer || isa<mlir_ts::ExtractPropertyOp, mlir_ts::DeconstructTupleOp>(producer))
{
return true;
continue;
}

// a local that owns what it holds (`{ x: a }`, #454's shape), or a literal
// nested in this one
if (readsLocalSlot(stored) || readsLiteralSlot(mth, location, stored))
if (auto storedLoad = dyn_cast<mlir_ts::LoadOp>(producer))
{
return true;
auto *place = storedLoad.getReference().getDefiningOp();
if ((place && isa<mlir_ts::PropertyRefOp, mlir_ts::ElementRefOp>(place)) ||
readsLocalSlot(stored) || readsLiteralSlot(mth, location, stored))
{
return true;
}

continue;
}

if (isa<mlir_ts::ConstantOp>(producer) &&
!isa<mlir_ts::ConstArrayType, mlir_ts::ConstTupleType>(stored.getType()))
{
continue;
}

// made for the literal
return true;
}
}

Expand Down
9 changes: 9 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ tslang_add_test(NAME test-compile-00-array-rest-copy COMMAND test-runner "${PROJ
tslang_add_test(NAME test-compile-00-arrays6 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array6.ts")
tslang_add_test(NAME test-compile-00-array-static-nested COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_static_nested.ts")
tslang_add_test(NAME test-compile-00-array-literal-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_literal_copy.ts")
tslang_add_test(NAME test-compile-00-const-record-owned-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-compile-00-string-view-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
tslang_add_test(NAME test-compile-00-array-data-string-alias COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_data_string_alias.ts")
Expand Down Expand Up @@ -852,6 +853,7 @@ tslang_add_test(NAME test-jit-00-array-rest-copy COMMAND test-runner -jit "${PRO
tslang_add_test(NAME test-jit-00-arrays6 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array6.ts")
tslang_add_test(NAME test-jit-00-array-static-nested COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_static_nested.ts")
tslang_add_test(NAME test-jit-00-array-literal-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_literal_copy.ts")
tslang_add_test(NAME test-jit-00-const-record-owned-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
tslang_add_test(NAME test-jit-00-string-view-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_view_copy.ts")
tslang_add_test(NAME test-jit-00-array-data-string-alias COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_data_string_alias.ts")
Expand Down Expand Up @@ -1685,6 +1687,7 @@ set(TSLANG_CORPUS
00array6.ts
00array_static_nested.ts
00array_literal_copy.ts
00const_record_owned_fields.ts
00array_length_valid.ts
00string_view_copy.ts
00array_data_string_alias.ts
Expand Down Expand Up @@ -2703,6 +2706,12 @@ add_test(NAME test-own-string-copy-in-store-block
"-DFILE=${PROJECT_SOURCE_DIR}/test/tester/own/own_string_copy_branch.ts"
-P "${CMAKE_CURRENT_SOURCE_DIR}/own-string-copy-block.cmake")

# A `const` record literal's slot is released under rc (#485); no run can see the leak, so the IR is read.
add_test(NAME test-rc-const-record-release
COMMAND ${CMAKE_COMMAND} -DTSLANG=$<TARGET_FILE:tslang>
"-DFILE=${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts"
-P "${CMAKE_CURRENT_SOURCE_DIR}/const-record-release.cmake")

# Shared<T> (spec 23): counted handles. These are not in the own_test list, whose
# test-own-no-counting check forbids __tslang_inc_ref: a handle is counted under own by design.
# own_shared_user_named: a program's own class named Shared wins over the built-in (spec 23.1).
Expand Down
28 changes: 28 additions & 0 deletions tslang/test/tester/const-record-release.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# A `const` record literal built at run time is a variable whose slot is released (#485): under rc
# each of the seven records in 00const_record_owned_fields.ts has a `ts.ReleaseSlot` of its tuple
# slot. Folded, as before, nothing counted an array or tuple built in the literal, and no run shows
# the leak that left, so the IR is read.
cmake_minimum_required(VERSION 3.17.3)
execute_process(COMMAND "${TSLANG}" --emit=mlir-affine --no-default-lib -mm=rc "${FILE}"
OUTPUT_VARIABLE out ERROR_VARIABLE err RESULT_VARIABLE result)
# the IR is printed to stderr
string(APPEND out "${err}")
if(NOT result EQUAL 0)
message(FATAL_ERROR "tslang failed (${result}):\n${out}")
endif()
foreach(function arrayField stringField callField newField tupleField newArrayField nestedField)
string(FIND "${out}" "ts.Func @${function} " begin)
if(begin EQUAL -1)
message(FATAL_ERROR "no function `${function}` in:\n${out}")
endif()
string(SUBSTRING "${out}" ${begin} -1 body)
string(SUBSTRING "${body}" 1 -1 rest)
string(FIND "${rest}" "ts.Func @" end)
if(NOT end EQUAL -1)
math(EXPR end "${end} + 1")
string(SUBSTRING "${body}" 0 ${end} body)
endif()
if(NOT body MATCHES "\"ts[.]ReleaseSlot\"[(]%[0-9]+[)] : [(]!ts[.]ref<!ts[.]tuple<")
message(FATAL_ERROR "`${function}` does not release its record's slot:\n${body}")
endif()
endforeach()
79 changes: 79 additions & 0 deletions tslang/test/tester/tests/00const_record_owned_fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// A `const` record literal built at run time keeps, and gives back, what each field holds (#485).
// Folded, nothing counted an array or tuple built in the literal: under rc and own every one
// leaked. The const now has storage, as a `let` has; const-record-release.cmake checks under rc
// that each record's slot is released. Here each kind of field is read after allocations of the
// same size have reused anything freed.
class C {
v: number[] = [];
}

type Numbers = number[];

function mk(i: number): number[] {
return [i, 2, 3];
}

function churn() {
let keep: number[][] = [];
for (let i = 0; i < 200; i++) keep.push([-i, -i, -i]);
return keep.length;
}

function arrayField(i: number) {
const o = { items: [i, 2], n: i };
o.items.push(3);
churn();
return o.items.length == 3 && o.items[0] == i && o.n == i;
}

function stringField(i: number) {
const o = { s: "a" + i, n: i };
churn();
return o.s == "a" + i;
}

function callField(i: number) {
const o = { items: mk(i), n: i };
churn();
return o.items.length == 3 && o.items[0] == i;
}

function newField(i: number) {
const o = { c: new C(), n: i };
o.c.v.push(i);
churn();
return o.c.v.length == 1 && o.c.v[0] == i;
}

function tupleField(i: number) {
const o = { t: [i, [i]], n: i };
churn();
return o.t[1].length == 1 && o.t[1][0] == i;
}

function newArrayField(i: number) {
const o = { items: new Numbers(i % 5 + 1), n: i };
churn();
return o.items.length == i % 5 + 1;
}

function nestedField(i: number) {
const o = { inner: { items: [i, 2], k: i }, n: i };
o.inner.items.push(3);
churn();
return o.inner.items.length == 3 && o.inner.items[0] == i;
}

function main() {
for (let i = 0; i < 100; i++) {
assert(arrayField(i), "an array built in the record");
assert(stringField(i), "a string made for the record");
assert(callField(i), "a call's result");
assert(newField(i), "a new instance");
assert(tupleField(i), "a tuple holding an array");
assert(newArrayField(i), "a new array");
assert(nestedField(i), "a record in the record");
}

print("done.");
}
Loading