diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index d8f80a56e..5230cf078 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1319,6 +1319,59 @@ namespace mlirgen return mlir::failure(); } + // If the destination has a known field order (e.g. a type-literal + // annotation on the receiving var/let), re-lay-out this literal's own + // storage to match it BEFORE method bodies are generated. Without + // this, the literal's storage stays in "all fields, then all + // methods" order (mlirGenObjectLiteralFields/-MethodPrototypes run as + // two separate passes over the properties, by design - methods may + // need every field already registered), which only coincidentally + // matches a declared type whose members interleave fields and + // methods in a different order. Method bodies compiled against the + // MISMATCHED order silently read/write the wrong byte offset when + // later invoked against the (differently laid out) destination + // memory - no crash, no error, just corrupted data: a `this.field` + // write can land on a neighboring method's function-pointer slot. + // Field position resolution inside method bodies is by NAME against + // objectStorageType's CURRENT field list (TupleFieldType/getIndex, + // MLIRCodeLogic.h), so reordering here - before + // mlirGenObjectLiteralMethodBodies runs - is sufficient; no + // per-method index patching needed. Must run after + // mlirGenObjectLiteralCaptures (methodInfosWithCaptures already holds + // pre-reorder indices consumed there) and before the final + // setFields/method-bodies pass below. + llvm::SmallVector receiverFields; + if (oli.receiverType && mlir::succeeded(mth.getFields(oli.receiverType, receiverFields, true)) && + receiverFields.size() == oli.fieldInfos.size()) + { + llvm::SmallVector reorderedValues; + llvm::SmallVector reorderedFieldInfos; + reorderedValues.reserve(oli.fieldInfos.size()); + reorderedFieldInfos.reserve(oli.fieldInfos.size()); + + auto allMatched = true; + for (auto &receiverField : receiverFields) + { + auto foundIt = llvm::find_if(oli.fieldInfos, + [&](const mlir_ts::FieldInfo &fieldInfo) { return fieldInfo.id == receiverField.id; }); + if (foundIt == oli.fieldInfos.end()) + { + allMatched = false; + break; + } + + auto index = std::distance(oli.fieldInfos.begin(), foundIt); + reorderedFieldInfos.push_back(*foundIt); + reorderedValues.push_back(oli.values[index]); + } + + if (allMatched) + { + oli.fieldInfos = std::move(reorderedFieldInfos); + oli.values = std::move(reorderedValues); + } + } + // final type, update objectStorageType.setFields(oli.fieldInfos); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 1b5724478..27a61ddc9 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -289,6 +289,7 @@ add_test(NAME test-compile-00-interface-function-typed-field COMMAND test-runner add_test(NAME test-compile-00-interface-captures COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts") 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-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") @@ -645,6 +646,7 @@ add_test(NAME test-jit-00-interface-function-typed-field COMMAND test-runner -ji add_test(NAME test-jit-00-interface-captures COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_captures.ts") 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-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") @@ -857,6 +859,7 @@ add_test(NAME test-compile-shared-export-import-object-literal-untyped COMMAND t add_test(NAME test-compile-shared-export-import-object-literal-structural-typed COMMAND test-runner -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed.ts") 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-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") @@ -876,6 +879,7 @@ add_test(NAME test-jit-shared-export-import-object-literal-untyped COMMAND test- add_test(NAME test-jit-shared-export-import-object-literal-structural-typed COMMAND test-runner -jit -shared -gctors-as-method "${PROJECT_SOURCE_DIR}/test/tester/tests/import_object_literal_structural_typed.ts" "${PROJECT_SOURCE_DIR}/test/tester/tests/export_object_literal_structural_typed.ts") 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-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_interleaved.ts b/tslang/test/tester/tests/00object_annotated_method_interleaved.ts new file mode 100644 index 000000000..590c104bb --- /dev/null +++ b/tslang/test/tester/tests/00object_annotated_method_interleaved.ts @@ -0,0 +1,40 @@ +// Extends 00object_annotated_method_params.ts's coverage: that test's +// type-literal annotations always declare all fields before all methods +// (total; add(); addTwice()). Here fields and methods INTERLEAVE in +// declaration order (base; addBase(); total; add(); ...) - this used to +// silently corrupt data: the object literal's own storage laid out as +// "fields, then methods" regardless of the annotation's order, so a method +// compiled against that layout read/wrote the wrong byte offset once +// invoked against the annotation-ordered variable, up to overwriting a +// sibling method's function pointer with a field's value. See +// docs/object-literal-boxing-design.md-adjacent history; root-caused and +// fixed via a field/value reorder pass in mlirGen(ObjectLiteralExpression). + +function main() { + let acc: { base: number; addBase(n: number): void; total: number; add(n: number): void; addTwice(n: number): void; scaled(factor: number): number } = { + base: 100, + addBase(n: number) { this.base = this.base + n; }, + total: 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; }, + }; + + 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_interleaved.ts b/tslang/test/tester/tests/export_object_literal_structural_typed_interleaved.ts new file mode 100644 index 000000000..ff4c332df --- /dev/null +++ b/tslang/test/tester/tests/export_object_literal_structural_typed_interleaved.ts @@ -0,0 +1,33 @@ +namespace A { + + export interface Accumulator { + base: number; + addBase(n: number): void; + total: number; + add(n: number): void; + addTwice(n: number): void; + scaled(factor: number): number; + } + + // extends export_object_literal_structural_typed_multi_method.ts's + // coverage: that test's methods are all grouped after every field + // (total; add(); addTwice(); scaled()). Here fields and methods + // INTERLEAVE in declaration order (base; addBase(); total; add(); ...) - + // see 00object_annotated_method_interleaved.ts for the same-module + // version of this coverage and the mechanism this used to break. + 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_interleaved.ts b/tslang/test/tester/tests/import_object_literal_structural_typed_interleaved.ts new file mode 100644 index 000000000..031063d4d --- /dev/null +++ b/tslang/test/tester/tests/import_object_literal_structural_typed_interleaved.ts @@ -0,0 +1,20 @@ +import './export_object_literal_structural_typed_interleaved' + +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.");