diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index 1694d0c8e..c491ea9b3 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1695,6 +1695,89 @@ namespace mlirgen return createArrayFromArrayInfo(location, values, arrayInfo, genContext); } + // A record literal whose receiver is a class (`const p: P = { x: i, items: [i] }`, a P + // parameter, a P result), built as the instance itself from its fields' values (#492). Not + // for a class with a base (its storage holds the base's fields elsewhere), nor for a literal + // missing one of the class's fields or holding one it has not, nor for a constant field other + // than a number, a string or a boolean: those go through the slot and castTupleToClass. + std::optional MLIRGenImpl::mlirGenObjectLiteralAsClassInstance(mlir::Location location, + ObjectLiteralInfo &oli, const GenContext &genContext) + { + auto classType = dyn_cast_or_null(oli.receiverType); + if (!classType) + { + return std::nullopt; + } + + auto classInfo = getClassInfoByFullName(classType.getName().getValue()); + if (!classInfo || !classInfo->baseClasses.empty()) + { + return std::nullopt; + } + + auto classFields = mlir::cast(classType.getStorageType()).getFields(); + SmallVector values; + auto matched = 0; + for (auto &classField : classFields) + { + auto name = dyn_cast_or_null(classField.id); + if (name && name.getValue().starts_with(".")) + { + continue; + } + + auto found = llvm::find_if(oli.fieldInfos, [&](auto &fieldInfo) { return fieldInfo.id == classField.id; }); + if (found == oli.fieldInfos.end()) + { + return std::nullopt; + } + + auto index = std::distance(oli.fieldInfos.begin(), found); + auto toSet = llvm::find_if(oli.fieldsToSet, [&](auto &fieldToSet) { return fieldToSet.first == classField.id; }); + if (toSet != oli.fieldsToSet.end()) + { + values.push_back(toSet->second); + } + else if (isa(oli.values[index])) + { + values.push_back(builder.create(location, found->type, oli.values[index])); + } + else if (auto arrayAttr = dyn_cast(oli.values[index])) + { + // a constant array (`items: []`, `items: [1, 2]`): a heap array of the instance's own, + // as in a literal built in a slot (#479) + auto arrayType = dyn_cast(found->type); + if (!arrayType) + { + return std::nullopt; + } + + auto literal = builder.create( + location, getConstArrayType(arrayType.getElementType(), arrayAttr.size()), arrayAttr); + auto copied = cast(location, arrayType, literal, genContext); + if (copied.failed_or_no_value()) + { + return ValueOrLogicalResult(mlir::failure()); + } + + values.push_back(V(copied)); + } + else + { + return std::nullopt; + } + + matched++; + } + + if (matched != static_cast(oli.fieldInfos.size())) + { + return std::nullopt; + } + + return NewClassInstanceWithSettingFields(location, classType, classFields, values, genContext); + } + ValueOrLogicalResult MLIRGenImpl::mlirGen(ts::ObjectLiteralExpression objectLiteral, const GenContext &genContext) { auto location = loc(objectLiteral); @@ -1835,6 +1918,19 @@ namespace mlirgen (objectLiteral->internalFlags & InternalFlags::BoxAsObject) == InternalFlags::BoxAsObject || !oli.methodInfos.empty() || !oli.methodInfosWithCaptures.empty(); + // A literal made into a class instance (`const p: P = { x: i, items: [i] }`) is the instance: + // each field's value is stored into a new P as it is, where it was stored into a slot the + // cast to P then read again - a read -mm=own takes for a borrow of the slot, which cannot + // be stored (#492). Only a literal whose fields are exactly the class's, and whose constant + // fields are plain values; any other goes through the slot and the cast (castTupleToClass). + if (!boxAsObject) + { + if (auto classInstance = mlirGenObjectLiteralAsClassInstance(location, oli, genContext)) + { + return *classInstance; + } + } + // A literal built in a slot (a field known only at run time, or boxed) starts from the // constant below, and an array kept in it would be a static header over constant data, // which `push`, `pop` and element writes change (#479): such a field is set in the slot as a diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index f9741ccbe..3498e57a6 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -12169,9 +12169,12 @@ class MLIRGenImpl ValueOrLogicalResult castTupleToTuple(mlir::Location location, mlir::Value value, mlir_ts::TupleType srcTupleType, ArrayRef fields, const GenContext &genContext, bool errorAsWarning = false); - ValueOrLogicalResult NewClassInstanceWithSettingFields(mlir::Location location, mlir_ts::ClassType classType, + ValueOrLogicalResult NewClassInstanceWithSettingFields(mlir::Location location, mlir_ts::ClassType classType, ArrayRef fields, ArrayRef values, const GenContext &genContext); + std::optional mlirGenObjectLiteralAsClassInstance(mlir::Location location, ObjectLiteralInfo &oli, + const GenContext &genContext); + ValueOrLogicalResult castTupleToClass(mlir::Location location, mlir::Value value, mlir_ts::TupleType srcTupleType, ArrayRef fields, mlir_ts::ClassType classType, const GenContext &genContext, bool errorAsWarning = false); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 11cf1224b..1af2da335 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -350,6 +350,7 @@ tslang_add_test(NAME test-compile-00-array-static-nested COMMAND test-runner "${ tslang_add_test(NAME test-compile-00-array-literal-copy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_literal_copy.ts") tslang_add_test(NAME test-compile-00-tuple-runtime-arrays COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00tuple_runtime_arrays.ts") tslang_add_test(NAME test-compile-00-record-to-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_to_class.ts") +tslang_add_test(NAME test-compile-00-record-variable-to-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_variable_to_class.ts") tslang_add_test(NAME test-compile-00-array-insert-functions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_insert_functions.ts") tslang_add_test(NAME test-compile-00-await-void COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00await_void.ts") tslang_add_test(NAME test-compile-00-for-await-sequential COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts") @@ -860,6 +861,7 @@ tslang_add_test(NAME test-jit-00-array-static-nested COMMAND test-runner -jit "$ tslang_add_test(NAME test-jit-00-array-literal-copy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_literal_copy.ts") tslang_add_test(NAME test-jit-00-tuple-runtime-arrays COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00tuple_runtime_arrays.ts") tslang_add_test(NAME test-jit-00-record-to-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_to_class.ts") +tslang_add_test(NAME test-jit-00-record-variable-to-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00record_variable_to_class.ts") tslang_add_test(NAME test-jit-00-array-insert-functions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_insert_functions.ts") tslang_add_test(NAME test-jit-00-await-void COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00await_void.ts") tslang_add_test(NAME test-jit-00-for-await-sequential COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00for_await_sequential.ts") @@ -1699,6 +1701,7 @@ set(TSLANG_CORPUS 00array_literal_copy.ts 00tuple_runtime_arrays.ts 00record_to_class.ts + 00record_variable_to_class.ts 00array_insert_functions.ts 00await_void.ts 00for_await_sequential.ts diff --git a/tslang/test/tester/tests/00record_to_class.ts b/tslang/test/tester/tests/00record_to_class.ts index f907eb7ac..6ed042798 100644 --- a/tslang/test/tester/tests/00record_to_class.ts +++ b/tslang/test/tester/tests/00record_to_class.ts @@ -30,11 +30,6 @@ function main() { e.items.push(i); assert(e.items.length == 1 && e.items[0] == 7, "an empty array"); - let n: number = 5; - let r = { x: n, items: [n, 2], name: "b" + n }; - const q: P = r; - q.items.push(9); - assert(r.items.length == 3 && q.name == "b5", "a record variable, sharing its array"); assert(take({ x: 2, items: [i], name: "cc" + i }) == 4, "to a class parameter"); const m = make(i); diff --git a/tslang/test/tester/tests/00record_variable_to_class.ts b/tslang/test/tester/tests/00record_variable_to_class.ts new file mode 100644 index 000000000..9e4cf9d14 --- /dev/null +++ b/tslang/test/tester/tests/00record_variable_to_class.ts @@ -0,0 +1,17 @@ +// A record variable assigned to a class type (#487): the instance is made from its fields and +// shares its arrays, which are references. Under -mm=own that is a second reference to the +// record's array, and an error; a record literal is not one (00record_to_class.ts, #492). +class P { + x: number; + items: number[]; + name: string; +} + +function main() { + let n: number = 5; + let r = { x: n, items: [n, 2], name: "b" + n }; + const q: P = r; + q.items.push(9); + assert(r.items.length == 3 && q.name == "b5", "a record variable, sharing its array"); + print("done."); +}