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
7 changes: 6 additions & 1 deletion tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,8 @@ class OwnedReturnConsumptionPass
// The initializer region ends in `ts.GlobalResult`, a terminator, so a value given to it
// straight is never released as a discarded temporary (搂9.30): `let g = mk()` keeps the call's
// +1. A value given to it through a view of the global's type is: `ts.Cast` to `C | null` or
// `Shared<T> | null`, `ts.OptionalValue` to `C | undefined`, `ts.CreateUnionInstance` - the view
// `Shared<T> | null`, `ts.OptionalValue` to `C | undefined`, `ts.CreateUnionInstance`,
// `ts.NewInterface` over a class object for an interface-typed global (`let g: I = mk()`) - the view
// is the one use the producer has, and it is releasable, so the producer's reference was given
// back at the end of the region and the global was born at count 0, pointing at freed memory
// (#459; own spec 23.1 for handles).
Expand Down Expand Up @@ -739,6 +740,10 @@ class OwnedReturnConsumptionPass
{
value = createUnionInstanceOp.getIn();
}
else if (auto newInterfaceOp = mlir::dyn_cast<mlir_ts::NewInterfaceOp>(definingOp))
{
value = newInterfaceOp.getThisVal();
}
else
{
break;
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 @@ -399,6 +399,7 @@ tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${P
tslang_add_test(NAME test-compile-00-const-interface-of-local COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.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-global-copy-counted COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.ts")
tslang_add_test(NAME test-compile-00-global-interface-initializer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_interface_initializer.ts")
tslang_add_test(NAME test-compile-00-c-string-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00c_string_copy.ts")
tslang_add_test(NAME test-compile-00-const-field-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_field_read.ts")
tslang_add_test(NAME test-compile-00-const-element-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_element_read.ts")
Expand Down Expand Up @@ -978,6 +979,7 @@ tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${
tslang_add_test(NAME test-jit-00-const-interface-of-local COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_of_local.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-global-copy-counted COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_copy_counted.ts")
tslang_add_test(NAME test-jit-00-global-interface-initializer COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_interface_initializer.ts")
tslang_add_test(NAME test-jit-00-c-string-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00c_string_copy.ts")
tslang_add_test(NAME test-jit-00-const-field-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_field_read.ts")
tslang_add_test(NAME test-jit-00-const-element-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_element_read.ts")
Expand Down Expand Up @@ -1947,6 +1949,7 @@ set(TSLANG_CORPUS
00const_interface_of_local.ts
00string_view_copy.ts
00global_copy_counted.ts
00global_interface_initializer.ts
00c_string_copy.ts
00const_field_read.ts
00const_element_read.ts
Expand Down Expand Up @@ -3546,6 +3549,12 @@ foreach(kept_mm own rc none gc)
tslang_add_test(NAME test-compile-${kept_mm}-own-global-kept-argument COMMAND test-runner -mm=${kept_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_global_kept_argument.ts")
endforeach()

# An interface-typed global given a fresh class object owns it; the same program under every model.
foreach(iface_mm own rc none gc)
tslang_add_test(NAME test-jit-${iface_mm}-own-global-interface-fresh COMMAND test-runner -jit -mm=${iface_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_global_interface_fresh.ts")
tslang_add_test(NAME test-compile-${iface_mm}-own-global-interface-fresh COMMAND test-runner -mm=${iface_mm} "${PROJECT_SOURCE_DIR}/test/tester/own/own_global_interface_fresh.ts")
endforeach()

# A `let` given a new value after its value moved owns the new one (#528); the same program under
# rc and gc (not none: nothing is freed there, and the loops would hold about 800 MB).
foreach(reassign_mm rc gc)
Expand Down
50 changes: 50 additions & 0 deletions tslang/test/tester/own/own_global_interface_fresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// An interface-typed global given a fresh class object (`let a: I = mk()`, `let a: I = new V()`)
// owns it. The object reaches the global through a view, `ts.NewInterface`, which hid it from the
// global's initializer: the call's or the `new`'s reference was given back as a discarded
// temporary at the end of the initializer, and the global was born pointing at freed memory
// (own and rc). Each read follows allocations of the same size, so a freed block is reused.
interface I {
x: number;
tag: string;
}

class V implements I {
tag = "";
constructor(public x: number) {
this.tag = "v" + x;
}
}

class Junk {
a = -1;
b = -2;
tag = "junk";
}

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

function mk(n: number) {
return new V(n);
}

let fromCall: I = mk(7);
let fromNew: I = new V(8);
const constFromCall: I = mk(9);

function main() {
churn();
assert(fromCall.x == 7 && fromCall.tag == "v7", "a call's result");
assert(fromNew.x == 8 && fromNew.tag == "v8", "a new object");
assert(constFromCall.x == 9 && constFromCall.tag == "v9", "a const made by a call");

fromCall = new V(17);
fromNew = mk(18);
churn();
assert(fromCall.x == 17 && fromCall.tag == "v17", "the call's global reassigned");
assert(fromNew.x == 18 && fromNew.tag == "v18", "the new object's global reassigned");
print("done.");
}
97 changes: 97 additions & 0 deletions tslang/test/tester/tests/00global_interface_initializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// An interface-typed global initialized with a class object (`let a: I = K.w`, `let a: I = mk()`)
// holds the object through a view, `ts.NewInterface`. Under rc that view hid the object from the
// global's initializer: a read of another global, a field or an element took no count, so giving
// either side a new value freed the block the other still read; and a call's or a `new`'s
// reference was given back as a discarded temporary at the end of the initializer, so the global
// was born pointing at freed memory. Each read follows allocations of the same size, so a freed
// block is reused.
interface I {
x: number;
tag: string;
}

class V implements I {
tag = "";
constructor(public x: number) {
this.tag = "v" + x;
}
}

class H {
v = new V(3);
}

class K {
static w = new V(1);
}

class Junk {
a = -1;
b = -2;
tag = "junk";
}

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

function mk(n: number) {
return new V(n);
}

let G = new V(2);
const h = new H();
const arr = [new V(4)];

// the interface global is given a new value: the source keeps its block
let aStatic: I = K.w;
let aGlobal: I = G;
let aField: I = h.v;
let aElement: I = arr[0];

// the source is given a new value: the interface global keeps its block
let G2 = new V(5);
const h2 = new H();
const arr2 = [new V(6)];
let bGlobal: I = G2;
const bConst: I = G2;
let bField: I = h2.v;
let bElement: I = arr2[0];

// a fresh object: the global takes its reference
let cCall: I = mk(7);
let cNew: I = new V(8);

function main() {
churn();
assert(cCall.x == 7 && cCall.tag == "v7", "a call's result");
assert(cNew.x == 8 && cNew.tag == "v8", "a new object");

aStatic = new V(11);
aGlobal = new V(12);
aField = new V(13);
aElement = new V(14);
churn();
assert(K.w.x == 1 && K.w.tag == "v1", "a static field read");
assert(G.x == 2 && G.tag == "v2", "a global read");
assert(h.v.x == 3 && h.v.tag == "v3", "a field read");
assert(arr[0].x == 4 && arr[0].tag == "v4", "an element read");

G2 = new V(15);
h2.v = new V(16);
arr2[0] = new V(17);
churn();
assert(bGlobal.x == 5 && bGlobal.tag == "v5", "a global read, the global reassigned");
assert(bConst.x == 5 && bConst.tag == "v5", "a const of a global, the global reassigned");
assert(bField.x == 3 && bField.tag == "v3", "a field read, the field reassigned");
assert(bElement.x == 6 && bElement.tag == "v6", "an element read, the element reassigned");

cCall = new V(18);
cNew = new V(19);
churn();
assert(cCall.x == 18 && cNew.x == 19, "the fresh objects' globals reassigned");
assert(aStatic.x == 11 && aGlobal.x == 12 && aField.x == 13 && aElement.x == 14, "the new values");
print("done.");
}
Loading