Skip to content
46 changes: 38 additions & 8 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6490,14 +6490,44 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern<mlir_ts::InterfaceSym
thisValEffective = rewriter.create<LLVM::SelectOp>(loc, isMissing, nullThisVal, thisVal);
}

auto thisOpaque =
rewriter.create<mlir_ts::CastOp>(loc, mlir_ts::OpaqueType::get(rewriter.getContext()), thisValEffective);
auto methodTypedPtr = rewriter.create<mlir_ts::CastOp>(
loc, mlir_ts::FunctionType::get(rewriter.getContext(), boundFunc.getInputs(), boundFunc.getResults()),
methodOrFieldPtr);
auto boundFuncVal =
rewriter.create<mlir_ts::CreateBoundFunctionOp>(loc, boundFunc, thisOpaque, methodTypedPtr);
rewriter.replaceOp(interfaceSymbolRefOp, ValueRange{boundFuncVal});
if (!tsLlvmContext->compileOptions.isRefCounted())
{
auto thisOpaque = rewriter.create<mlir_ts::CastOp>(
loc, mlir_ts::OpaqueType::get(rewriter.getContext()), thisValEffective);
auto methodTypedPtr = rewriter.create<mlir_ts::CastOp>(
loc, mlir_ts::FunctionType::get(rewriter.getContext(), boundFunc.getInputs(), boundFunc.getResults()),
methodOrFieldPtr);
auto boundFuncVal =
rewriter.create<mlir_ts::CreateBoundFunctionOp>(loc, boundFunc, thisOpaque, methodTypedPtr);
rewriter.replaceOp(interfaceSymbolRefOp, ValueRange{boundFuncVal});
return success();
}

// Under rc a method bound to an object counts it through the object's type tag
// (getBoundObjectTypeTag), and a receiver of the bound value - `c = ix.get` - holds the
// object. Bound through an interface, the object's type is not known here, so the bound
// value had no tag, counted nothing, and `c()` ran on a freed object once the interface
// went. The interface carries the tag of what it points at (NewInterfaceOpLowering), and
// the bound value takes that one: null for a missing optional method, whose `this` is.
mlir::Value tag = rewriter.create<LLVM::ExtractValueOp>(
loc, th.getPtrType(), transformed.getInterfaceVal(), MLIRHelper::getStructIndex(rewriter, INTERFACE_TYPE_INDEX));
if (isOptional)
{
auto isNullThis = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, thisValEffective,
rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType()));
tag = rewriter.create<LLVM::SelectOp>(loc, isNullThis, rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType()), tag);
}

// built here as CreateBoundFunctionOpLowering builds it, rather than over a
// ts.CreateBoundFunction still waiting for its own conversion
auto structVal = rewriter.create<LLVM::UndefOp>(loc, tch.convertType(boundFunc));
auto structVal2 = rewriter.create<LLVM::InsertValueOp>(loc, structVal, methodOrFieldPtr,
MLIRHelper::getStructIndex(rewriter, DATA_VALUE_INDEX));
auto structVal3 = rewriter.create<LLVM::InsertValueOp>(loc, structVal2, thisValEffective,
MLIRHelper::getStructIndex(rewriter, THIS_VALUE_INDEX));
auto structVal4 = rewriter.create<LLVM::InsertValueOp>(loc, structVal3, tag,
MLIRHelper::getStructIndex(rewriter, CLOSURE_TYPE_INDEX));
rewriter.replaceOp(interfaceSymbolRefOp, ValueRange{structVal4});
}
else
{
Expand Down
17 changes: 17 additions & 0 deletions tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,23 @@ class OwnedReturnConsumptionPass
mlir::OpBuilder builder(loadOp->getContext());
builder.setInsertionPointAfter(loadOp);
builder.create<mlir_ts::RetainOp>(loadOp->getLoc(), value);
return;
}

// A method bound to an object (`let c = mk().get`, `let c = G.v.get`, `let c = ix.get`)
// is born with no count, and only its receivers take one; under rc the bound value counts
// the object it is bound to. The global is the receiver here: without a count of its
// own, a temporary object was given back at the end of the region, and the first
// `c = d` released it a second time.
auto *boundOp = value.getDefiningOp();
auto isBoundMethod =
mlir::isa_and_nonnull<mlir_ts::ThisVirtualSymbolRefOp, mlir_ts::ThisSymbolRefOp>(boundOp) ||
(mlir::isa_and_nonnull<mlir_ts::InterfaceSymbolRefOp>(boundOp) &&
mlir::isa<mlir_ts::BoundFunctionType>(value.getType())) ||
(mlir::isa_and_nonnull<mlir_ts::CreateBoundFunctionOp>(boundOp) && !boundOp->hasAttr(OWNS_CAPTURE_ATTR_NAME));
if (isBoundMethod)
{
retainForGlobal(resultOp, resultOp->getOperand(0));
}
});
}
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ tslang_add_test(NAME test-compile-00-const-element-read COMMAND test-runner "${P
tslang_add_test(NAME test-compile-00-const-global-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_global_read.ts")
tslang_add_test(NAME test-compile-00-global-argument-reassigned COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_reassigned.ts")
tslang_add_test(NAME test-compile-00-global-argument-view COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_view.ts")
tslang_add_test(NAME test-compile-00-bound-method-counts-object COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00bound_method_counts_object.ts")
tslang_add_test(NAME test-compile-00-closure-const-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00closure_const_read.ts")
tslang_add_test(NAME test-compile-00-element-store-after-value COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00element_store_after_value.ts")
tslang_add_test(NAME test-compile-00-const-interface-field-read COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_field_read.ts")
Expand Down Expand Up @@ -994,6 +995,7 @@ tslang_add_test(NAME test-jit-00-const-element-read COMMAND test-runner -jit "${
tslang_add_test(NAME test-jit-00-const-global-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_global_read.ts")
tslang_add_test(NAME test-jit-00-global-argument-reassigned COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_reassigned.ts")
tslang_add_test(NAME test-jit-00-global-argument-view COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00global_argument_view.ts")
tslang_add_test(NAME test-jit-00-bound-method-counts-object COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00bound_method_counts_object.ts")
tslang_add_test(NAME test-jit-00-closure-const-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00closure_const_read.ts")
tslang_add_test(NAME test-jit-00-element-store-after-value COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00element_store_after_value.ts")
tslang_add_test(NAME test-jit-00-const-interface-field-read COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_interface_field_read.ts")
Expand Down Expand Up @@ -1972,6 +1974,7 @@ set(TSLANG_CORPUS
00const_global_read.ts
00global_argument_reassigned.ts
00global_argument_view.ts
00bound_method_counts_object.ts
00closure_const_read.ts
00element_store_after_value.ts
00const_interface_field_read.ts
Expand Down
75 changes: 75 additions & 0 deletions tslang/test/tester/tests/00bound_method_counts_object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// A method bound to an object keeps that object alive while it is held: bound through an
// interface (`c = ig.get`), or held by a global from its initializer (`let c = mv(3).get`,
// `let c = G.v.get`). Under rc a bound value counts its object through the object's type tag, and
// only its receivers take a count. Through an interface it had no tag and counted nothing, and a
// global's initializer took no count, so the object was given back while the bound value still
// ran on it. Each read follows allocations of the same size, so a freed block is reused.
class V {
constructor(public x: number) {}

get(): number {
return this.x;
}
}

class N {
v = new V(4);
}

interface IG {
get(): number;
}

function mv(x: number) {
return new V(x);
}

function churn() {
const junk: V[] = [];
for (let i = 0; i < 64; i++) {
junk.push(new V(777));
}
}

let G = new N();
let fromTemporary: () => number = mv(3).get;
let fromField: () => number = G.v.get;

function boundThroughInterface(x: number) {
let c: () => number = mv(0).get;
{
const ig: IG = mv(x);
c = ig.get;
}

churn();
return c();
}

function globalBoundThroughInterface(x: number) {
{
let ig: IG = mv(x);
fromTemporary = ig.get;
}

churn();
return fromTemporary();
}

function main() {
churn();
assert(fromTemporary() == 3, "a global bound to a temporary object by its initializer");
G.v = mv(9);
churn();
assert(fromField() == 4, "a global bound to a global's field by its initializer");
fromField = mv(6).get;
churn();
assert(fromField() == 6, "a global bound again");

for (let i = 0; i < 3; i++) {
assert(boundThroughInterface(i + 10) == i + 10, "a local bound through an interface");
assert(globalBoundThroughInterface(i + 20) == i + 20, "a global bound through an interface");
}

print("done.");
}
Loading