diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h index e306cc002..62903a253 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h @@ -286,6 +286,23 @@ struct InterfaceInfo { } + // own methods plus every inherited method from `extends`, in the same + // extends-first-then-own order as getTupleTypeFields/getVirtualTable - + // needed wherever code must patch/visit every method slot a cast target + // could own, not just this interface's own directly-declared ones. + void getAllMethods(llvm::SmallVector &allMethods) + { + for (auto &extent : extends) + { + std::get<1>(extent)->getAllMethods(allMethods); + } + + for (auto &method : methods) + { + allMethods.push_back(&method); + } + } + mlir::LogicalResult getTupleTypeFields(llvm::SmallVector &tupleFields, mlir::MLIRContext *context) { for (auto &extent : extends) @@ -335,7 +352,7 @@ struct InterfaceInfo { for (auto &extent : extends) { - if (mlir::failed(std::get<1>(extent)->getVirtualTable(vtable, resolveField, resolveMethod))) + if (mlir::failed(std::get<1>(extent)->getVirtualTable(vtable, resolveField, resolveMethod, methodsAsFields))) { return mlir::failure(); } diff --git a/tslang/lib/TypeScript/MLIRGenInterfaces.cpp b/tslang/lib/TypeScript/MLIRGenInterfaces.cpp index b295a5b1c..6495f4097 100644 --- a/tslang/lib/TypeScript/MLIRGenInterfaces.cpp +++ b/tslang/lib/TypeScript/MLIRGenInterfaces.cpp @@ -196,7 +196,14 @@ namespace mlirgen auto globalVTableRefValue = resolveFullNameIdentifier(location, fullObjectInterfaceVTableFieldName, true, genContext); // we need to update methods references in VTable with functions from object; - if (newInterfacePtr->methods.size() > 0) { + // includes methods inherited via `extends`, not just this interface's own - + // an inherited method's vtable slot needs patching (or at least visiting) the + // same as an own one; only checking newInterfacePtr->methods here left every + // inherited method's slot holding its initial offset-placeholder value + // (never a real function pointer), crashing on the first call through it. + llvm::SmallVector allMethods; + newInterfacePtr->getAllMethods(allMethods); + if (allMethods.size() > 0) { mlir_ts::TupleType storeType; if (auto objectStoreType = dyn_cast(objectType.getStorageType())) @@ -220,8 +227,9 @@ namespace mlirgen // local funcOp to name) still need their function pointer read out of the // actual object `in` at cast time. llvm::SmallVector methodsNeedingPatch; - for (auto& method : newInterfacePtr->methods) + for (auto* methodPtr : allMethods) { + auto& method = *methodPtr; auto fieldId = builder.getStringAttr(method.name); auto index = mth.getFieldIndexByFieldName(storeType, fieldId); if (index == -1) @@ -232,7 +240,7 @@ namespace mlirgen auto fieldInfo = mth.getFieldInfoByIndex(storeType, index); if (lookupObjectLiteralMethodSymbol(fieldInfo.type, fieldId).empty()) { - methodsNeedingPatch.push_back(&method); + methodsNeedingPatch.push_back(methodPtr); } } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index c4525ac29..51334b111 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -290,6 +290,7 @@ add_test(NAME test-compile-00-interface-captures COMMAND test-runner "${PROJECT_ add_test(NAME test-compile-00-object-annotated-method COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts") add_test(NAME test-compile-00-object-annotated-method-params COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_params.ts") add_test(NAME test-compile-00-object-annotated-method-interleaved COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_interleaved.ts") +add_test(NAME test-compile-00-object-annotated-method-extends-interface COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_extends_interface.ts") add_test(NAME test-compile-00-interface-object-array COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object_array.ts") add_test(NAME test-compile-00-interface-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts") add_test(NAME test-compile-00-interface-new COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts") @@ -647,6 +648,7 @@ add_test(NAME test-jit-00-interface-captures COMMAND test-runner -jit "${PROJECT add_test(NAME test-jit-00-object-annotated-method COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method.ts") add_test(NAME test-jit-00-object-annotated-method-params COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_params.ts") add_test(NAME test-jit-00-object-annotated-method-interleaved COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_interleaved.ts") +add_test(NAME test-jit-00-object-annotated-method-extends-interface COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00object_annotated_method_extends_interface.ts") add_test(NAME test-jit-00-interface-object-array COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_object_array.ts") add_test(NAME test-jit-00-interface-generic COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_generic.ts") add_test(NAME test-jit-00-interface-new COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_new.ts") @@ -861,6 +863,7 @@ add_test(NAME test-compile-shared-export-import-object-literal-structural-typed add_test(NAME test-compile-shared-export-import-object-literal-structural-typed-params COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_params.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_params.ts") add_test(NAME test-compile-shared-export-import-object-literal-structural-typed-multi-method COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_multi_method.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_multi_method.ts") add_test(NAME test-compile-shared-export-import-object-literal-structural-typed-interleaved COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_interleaved.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_interleaved.ts") +add_test(NAME test-compile-shared-export-import-object-literal-structural-typed-extends-interface COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_extends_interface.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_extends_interface.ts") add_test(NAME test-compile-shared-export-import-vars COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars.ts") add_test(NAME test-compile-shared-export-import-vars-2 COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars2.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars2.ts") add_test(NAME test-compile-shared-export-import-enum COMMAND test-runner -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_enum.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_enum.ts") @@ -882,6 +885,7 @@ add_test(NAME test-jit-shared-export-import-object-literal-structural-typed COMM add_test(NAME test-jit-shared-export-import-object-literal-structural-typed-params COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_params.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_params.ts") add_test(NAME test-jit-shared-export-import-object-literal-structural-typed-multi-method COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_multi_method.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_multi_method.ts") add_test(NAME test-jit-shared-export-import-object-literal-structural-typed-interleaved COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_interleaved.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_interleaved.ts") +add_test(NAME test-jit-shared-export-import-object-literal-structural-typed-extends-interface COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed_extends_interface.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed_extends_interface.ts") add_test(NAME test-jit-shared-export-import-vars COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars.ts") add_test(NAME test-jit-shared-export-import-vars-2 COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_vars2.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_vars2.ts") add_test(NAME test-jit-shared-export-import-enum COMMAND test-runner -jit -shared "${PROJECT_SOURCE_DIR}/test/tester/tests/import_enum.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_enum.ts") diff --git a/tslang/test/tester/tests/00object_annotated_method_extends_interface.ts b/tslang/test/tester/tests/00object_annotated_method_extends_interface.ts new file mode 100644 index 000000000..9ccfb6a5d --- /dev/null +++ b/tslang/test/tester/tests/00object_annotated_method_extends_interface.ts @@ -0,0 +1,58 @@ +// Casting a structurally-typed object literal to an interface that EXTENDS +// another interface used to fail outright: interface B { base; addBase() } +// interface Accumulator extends B { total; add(); addTwice(); scaled() }. +// +// Two separate bugs, both in the interface vtable machinery: +// 1. InterfaceInfo::getVirtualTable's recursion into `extends` forgot to +// propagate the `methodsAsFields` flag to the recursive call, so an +// inherited (non-conditional) method always hit a stub resolver that +// unconditionally returns failure - the CAST STATEMENT ITSELF failed to +// compile ("error: failed statement", no specific diagnostic). +// 2. Once (1) was fixed, the interface cast's runtime vtable-patch loop +// (mlirGenCreateInterfaceVTableForObject) only walked the interface's +// OWN methods, never inherited ones - an inherited method's vtable slot +// was left holding its unpatched offset-placeholder value forever, +// crashing with an access violation on the first call through it. + +function main() { + interface Base { + base: number; + addBase(n: number): void; + } + + interface Accumulator extends Base { + total: number; + add(n: number): void; + addTwice(n: number): void; + scaled(factor: number): number; + } + + let raw = { + base: 100.0, + addBase(n: number) { this.base = this.base + n; }, + total: 0.0, + add(n: number) { this.total = this.total + n; }, + addTwice(n: number) { this.add(n); this.add(n); }, + scaled(factor: number) { return this.total * factor; }, + }; + + let acc: Accumulator = raw; + + acc.addBase(5); + assert(acc.base == 105); + print(acc.base); + + acc.add(3); + assert(acc.total == 3); + print(acc.total); + + acc.addTwice(2); + assert(acc.total == 7); + print(acc.total); + + const result = acc.scaled(2); + assert(result == 14); + print(result); + + print("done."); +} diff --git a/tslang/test/tester/tests/export_object_literal_structural_typed_extends_interface.ts b/tslang/test/tester/tests/export_object_literal_structural_typed_extends_interface.ts new file mode 100644 index 000000000..5dfe86363 --- /dev/null +++ b/tslang/test/tester/tests/export_object_literal_structural_typed_extends_interface.ts @@ -0,0 +1,37 @@ +namespace A { + + export interface Base { + base: number; + addBase(n: number): void; + } + + // extends export_object_literal_structural_typed_interleaved.ts's + // coverage: that test's interface is flat (no `extends`). Here + // Accumulator inherits base/addBase from Base - see + // 00object_annotated_method_extends_interface.ts for the same-module + // version of this coverage and the two bugs this used to hit (interface + // vtable construction and patching both need to walk inherited members, + // not just an interface's own directly-declared ones). + export interface Accumulator extends Base { + total: number; + add(n: number): void; + addTwice(n: number): void; + scaled(factor: number): number; + } + + export var acc: { + base: number; + addBase(n: number): void; + total: number; + add(n: number): void; + addTwice(n: number): void; + scaled(factor: number): number; + } = { + base: 100.0, + addBase(n: number) { this.base = this.base + n; }, + total: 0.0, + add(n: number) { this.total = this.total + n; }, + addTwice(n: number) { this.add(n); this.add(n); }, + scaled(factor: number) { return this.total * factor; }, + }; +} diff --git a/tslang/test/tester/tests/import_object_literal_structural_typed_extends_interface.ts b/tslang/test/tester/tests/import_object_literal_structural_typed_extends_interface.ts new file mode 100644 index 000000000..40adefa59 --- /dev/null +++ b/tslang/test/tester/tests/import_object_literal_structural_typed_extends_interface.ts @@ -0,0 +1,20 @@ +import './export_object_literal_structural_typed_extends_interface' + +var acc: A.Accumulator = A.acc; + +acc.addBase(5); +print(acc.base); +assert(acc.base == 105); + +acc.add(3); +print(acc.total); +assert(acc.total == 3); + +acc.addTwice(2); +print(acc.total); +assert(acc.total == 7); + +print(acc.scaled(2)); +assert(acc.scaled(2) == 14); + +print("done.");