diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 2854a8ca3..e8be34c68 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -6490,14 +6490,44 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern(loc, isMissing, nullThisVal, thisVal); } - auto thisOpaque = - rewriter.create(loc, mlir_ts::OpaqueType::get(rewriter.getContext()), thisValEffective); - auto methodTypedPtr = rewriter.create( - loc, mlir_ts::FunctionType::get(rewriter.getContext(), boundFunc.getInputs(), boundFunc.getResults()), - methodOrFieldPtr); - auto boundFuncVal = - rewriter.create(loc, boundFunc, thisOpaque, methodTypedPtr); - rewriter.replaceOp(interfaceSymbolRefOp, ValueRange{boundFuncVal}); + if (!tsLlvmContext->compileOptions.isRefCounted()) + { + auto thisOpaque = rewriter.create( + loc, mlir_ts::OpaqueType::get(rewriter.getContext()), thisValEffective); + auto methodTypedPtr = rewriter.create( + loc, mlir_ts::FunctionType::get(rewriter.getContext(), boundFunc.getInputs(), boundFunc.getResults()), + methodOrFieldPtr); + auto boundFuncVal = + rewriter.create(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( + loc, th.getPtrType(), transformed.getInterfaceVal(), MLIRHelper::getStructIndex(rewriter, INTERFACE_TYPE_INDEX)); + if (isOptional) + { + auto isNullThis = rewriter.create(loc, LLVM::ICmpPredicate::eq, thisValEffective, + rewriter.create(loc, th.getPtrType())); + tag = rewriter.create(loc, isNullThis, rewriter.create(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(loc, tch.convertType(boundFunc)); + auto structVal2 = rewriter.create(loc, structVal, methodOrFieldPtr, + MLIRHelper::getStructIndex(rewriter, DATA_VALUE_INDEX)); + auto structVal3 = rewriter.create(loc, structVal2, thisValEffective, + MLIRHelper::getStructIndex(rewriter, THIS_VALUE_INDEX)); + auto structVal4 = rewriter.create(loc, structVal3, tag, + MLIRHelper::getStructIndex(rewriter, CLOSURE_TYPE_INDEX)); + rewriter.replaceOp(interfaceSymbolRefOp, ValueRange{structVal4}); } else { diff --git a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp index ff721257e..1230869b4 100644 --- a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp +++ b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp @@ -1147,6 +1147,23 @@ class OwnedReturnConsumptionPass mlir::OpBuilder builder(loadOp->getContext()); builder.setInsertionPointAfter(loadOp); builder.create(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(boundOp) || + (mlir::isa_and_nonnull(boundOp) && + mlir::isa(value.getType())) || + (mlir::isa_and_nonnull(boundOp) && !boundOp->hasAttr(OWNS_CAPTURE_ATTR_NAME)); + if (isBoundMethod) + { + retainForGlobal(resultOp, resultOp->getOperand(0)); } }); } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 5cd4d7188..01a3b506d 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -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") @@ -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") @@ -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 diff --git a/tslang/test/tester/tests/00bound_method_counts_object.ts b/tslang/test/tester/tests/00bound_method_counts_object.ts new file mode 100644 index 000000000..d67dd4c6e --- /dev/null +++ b/tslang/test/tester/tests/00bound_method_counts_object.ts @@ -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."); +}