diff --git a/tslang/include/TypeScript/LowerToLLVM/LLVMCodeHelper.h b/tslang/include/TypeScript/LowerToLLVM/LLVMCodeHelper.h index 83c734370..e89ff5ab0 100644 --- a/tslang/include/TypeScript/LowerToLLVM/LLVMCodeHelper.h +++ b/tslang/include/TypeScript/LowerToLLVM/LLVMCodeHelper.h @@ -717,17 +717,13 @@ class LLVMCodeHelper : public LLVMCodeHelperBase { LLVM_DEBUG(llvm::dbgs() << "!! Unit Attr is type of '" << llvmType << "'\n"); - // An unspecified field of an owning type must be null rather than undef: under - // reference counting the tuple is retained as a whole before anything writes the - // field (a generator's state object is built exactly this way), and walking an - // undef pointer to reach its header is undefined behaviour, which the optimizer - // is entitled to - and does - fold the whole caller away for. - auto unspecifiedFieldIsOwning = compileOptions.tracksOwnership() && - MLIRTypeHelper(rewriter.getContext(), compileOptions).ownsHeapMemory(loc, type); - - mlir::Value itemValue = unspecifiedFieldIsOwning - ? rewriter.create(loc, llvmType).getResult() - : rewriter.create(loc, llvmType).getResult(); + // An unspecified field is zero rather than undef. It is read - an iterator's final + // `{ value: undefined, done: true }` is this constant - and under reference + // counting the tuple is retained as a whole before anything writes the field (a + // generator's state object is built exactly this way), and walking an undef + // pointer to reach its header is undefined behaviour, which the optimizer is + // entitled to - and does - fold the whole caller away for. + mlir::Value itemValue = rewriter.create(loc, llvmType).getResult(); tupleVal = rewriter.create(loc, tupleVal, itemValue, MLIRHelper::getStructIndex(rewriter, position++)); } diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index a0e6a922d..0d27a513c 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -1515,22 +1515,13 @@ class UndefOpLowering : public TsLlvmPattern TypeConverterHelper tch(getTypeConverter()); - // `undefined` materialised as a value of a type that owns heap memory has to be null - // rather than undef: under reference counting whoever receives it retains it, and - // reaching an undef pointer's block header is undefined behaviour. An iterator's final - // `{ value: undefined, done: true }` is built exactly this way, and the caller retains - // the result before it looks at `done`. - if (tsLlvmContext->compileOptions.tracksOwnership()) - { - MLIRTypeHelper mth(rewriter.getContext(), tsLlvmContext->compileOptions); - if (mth.ownsHeapMemory(op.getLoc(), op.getType())) - { - rewriter.replaceOpWithNewOp(op, tch.convertType(op.getType())); - return success(); - } - } - - rewriter.replaceOpWithNewOp(op, tch.convertType(op.getType())); + // `undefined` materialised as a value of a type that cannot hold it is zero, not undef. + // It is read: an iterator's final `{ value: undefined, done: true }` is built exactly + // this way, and an undef `value` printed whatever the register held (a Set's + // `values().drop(4).next()` gave `{ value: -971415704, done: true }`). Under reference + // counting it matters twice over - whoever receives an owning value retains it, and + // reaching an undef pointer's block header is undefined behaviour. + rewriter.replaceOpWithNewOp(op, tch.convertType(op.getType())); return success(); } }; diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index 9a3ec01b6..318127241 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1078,6 +1078,37 @@ namespace mlirgen EXIT_IF_FAILED_OR_NO_VALUE(result) auto value = V(result); + // `new Set([2, 4, 6, 8])`: a generic class written without type arguments takes them from + // its constructor's arguments (see inferClassTypeFromConstructorArguments). Ahead of the + // receiver below, which overrides it: `const s: Set = new Set([1, 2])` is Set. + if (newExpression->typeArguments.size() == 0 && newExpression->arguments.size() > 0) + { + if (auto classType = dyn_cast(value.getType())) + { + // the generic itself, or its specialization by the defaults + auto genericClassInfo = getGenericClassInfoByFullName(classType.getName().getValue()); + if (!genericClassInfo) + { + if (auto classInfo = getClassInfoByFullName(classType.getName().getValue()); + classInfo && classInfo->originClassType) + { + genericClassInfo = getGenericClassInfoByFullName(classInfo->originClassType.getName().getValue()); + } + } + + if (genericClassInfo) + { + if (auto inferredClassType = inferClassTypeFromConstructorArguments( + location, genericClassInfo, newExpression->arguments, genContext)) + { + value = builder.create( + location, inferredClassType, + mlir::FlatSymbolRefAttr::get(builder.getContext(), inferredClassType.getName().getValue())); + } + } + } + } + // `const m: Map = new Map()`: a generic class written without type // arguments takes them from the type it is given to, when that is the same class. Its // type parameter defaults (`Map`) made Map otherwise, a diff --git a/tslang/lib/TypeScript/MLIRGenGenerics.cpp b/tslang/lib/TypeScript/MLIRGenGenerics.cpp index 63dbf37e2..f12be9b57 100644 --- a/tslang/lib/TypeScript/MLIRGenGenerics.cpp +++ b/tslang/lib/TypeScript/MLIRGenGenerics.cpp @@ -1246,6 +1246,127 @@ namespace mlirgen return {mlir::success(), mlir::Type()}; } + // `new Set([2, 4, 6, 8])`: a generic class written without type arguments takes them from its + // constructor's arguments, as a generic function's call does (`first([2, 4])` is first). + // Nothing did: the class fell back to its defaults (`Set` made Set) or, with none, + // was "missing type arguments". A type parameter the arguments do not reach keeps its default; + // when one has neither, or anything is still generic, there is no answer here and the caller + // goes on as before. + mlir_ts::ClassType MLIRGenImpl::inferClassTypeFromConstructorArguments(mlir::Location location, + GenericClassInfo::TypePtr genericClassInfo, + NodeArray arguments, + const GenContext &genContext) + { + // one constructor: with overloads, which one the arguments pick is a question for the call + ConstructorDeclaration constructorDeclaration; + for (auto member : genericClassInfo->classDeclaration->members) + { + if (member == SyntaxKind::Constructor && !hasModifier(member, SyntaxKind::StaticKeyword)) + { + if (constructorDeclaration) + { + return mlir_ts::ClassType(); + } + + constructorDeclaration = member.as(); + } + } + + if (!constructorDeclaration) + { + return mlir_ts::ClassType(); + } + + // the arguments' types, in the caller's scope; a function argument's parameters are typed + // by the constructor's, which is what is being worked out, so it tells nothing + SmallVector argumentTypes; + for (auto argument : arguments) + { + if (argument == SyntaxKind::SpreadElement) + { + break; + } + + argumentTypes.push_back(argument == SyntaxKind::ArrowFunction || argument == SyntaxKind::FunctionExpression + ? mlir::Type() + : evaluate(argument, genContext)); + } + + // the parameters' types in the class's own scope, its type parameters standing for themselves + MLIRNamespaceGuard ng(currentNamespace); + currentNamespace = genericClassInfo->elementNamespace; + + SourceFileScope sourceFileScope(*this, genericClassInfo->sourceFile, genericClassInfo->fileName); + + GenContext templateGenContext(genContext); + for (auto &typeParam : genericClassInfo->typeParams) + { + templateGenContext.typeParamsWithArgs[typeParam->getName()] = {typeParam, getNamedGenericType(typeParam->getName())}; + } + + StringMap inferredTypes; + auto parameters = constructorDeclaration->parameters; + for (auto [index, argumentType] : enumerate(argumentTypes)) + { + if (index >= parameters.size()) + { + break; + } + + auto parameter = parameters[index]; + if (parameter->dotDotDotToken || !parameter->type || !argumentType) + { + continue; + } + + if (auto parameterType = getType(parameter->type, templateGenContext)) + { + inferType(location, parameterType, argumentType, inferredTypes, templateGenContext); + } + } + + // in declaration order, up to the first the arguments do not reach; the rest must have defaults + SmallVector typeArguments; + for (auto &typeParam : genericClassInfo->typeParams) + { + auto found = inferredTypes.find(typeParam->getName()); + if (found == inferredTypes.end()) + { + break; + } + + auto type = mth.wideStorageType(found->getValue()); + if (mth.isGenericType(type)) + { + return mlir_ts::ClassType(); + } + + typeArguments.push_back(type); + } + + if (typeArguments.empty()) + { + return mlir_ts::ClassType(); + } + + for (auto index = typeArguments.size(); index < genericClassInfo->typeParams.size(); index++) + { + if (!genericClassInfo->typeParams[index]->hasDefault()) + { + return mlir_ts::ClassType(); + } + } + + auto [result, specType] = + instantiateSpecializedClassType(location, genericClassInfo->classType, typeArguments, genContext); + if (mlir::failed(result)) + { + return mlir_ts::ClassType(); + } + + return dyn_cast_or_null(specType); + } + std::pair MLIRGenImpl::instantiateSpecializedInterfaceType( mlir::Location location, mlir_ts::InterfaceType genericInterfaceType, NodeArray typeArguments, const GenContext &genContext, bool allowNamedGenerics) diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 2e34ce712..8d45bd2af 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -1210,9 +1210,13 @@ class MLIRGenImpl bool tryInferTupleFields(mlir::Location location, mlir_ts::TupleType tempTuple, T typeTuple, StringMap &results, const GenContext &genContext) { - for (auto tempFieldInfo : tempTuple.getFields()) + for (auto [position, tempFieldInfo] : enumerate(tempTuple.getFields())) { - auto index = typeTuple.getIndex(tempFieldInfo.id); + // a field of `[K, V]` has no name, and every unnamed field looked up by its (null) id + // found the first: K and V both took the first element's type + auto index = tempFieldInfo.id + ? typeTuple.getIndex(tempFieldInfo.id) + : (position < typeTuple.size() ? static_cast(position) : -1); if (index >= 0) { inferType(location, tempFieldInfo.type, typeTuple.getFieldInfo(index).type, results, genContext); @@ -1294,6 +1298,11 @@ class MLIRGenImpl const GenContext &genContext, bool allowNamedGenerics = false); + mlir_ts::ClassType inferClassTypeFromConstructorArguments(mlir::Location location, + GenericClassInfo::TypePtr genericClassInfo, + NodeArray arguments, + const GenContext &genContext); + std::pair instantiateSpecializedInterfaceType( mlir::Location location, mlir_ts::InterfaceType genericInterfaceType, NodeArray typeArguments, const GenContext &genContext, bool allowNamedGenerics = false); diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index a90f0690b..f9e56648b 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -433,6 +433,8 @@ tslang_add_test(NAME test-compile-00-string-length-resize COMMAND test-runner "$ tslang_add_test(NAME test-compile-00-array-length-grow-zeroed COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_grow_zeroed.ts") tslang_add_test(NAME test-compile-00-rest-args-pack-owned COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00rest_args_pack_owned.ts") tslang_add_test(NAME test-compile-00-switch-char-string-cases COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_char_string_cases.ts") +tslang_add_test(NAME test-compile-00-generic-class-constructor-inference COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_class_constructor_inference.ts") +tslang_add_test(NAME test-compile-00-generator-done-value-zero COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_done_value_zero.ts") tslang_add_test(NAME test-compile-00-owned-generators COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generators.ts") tslang_add_test(NAME test-compile-00-owned-globals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_globals.ts") tslang_add_test(NAME test-compile-00-owned-generator-locals COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generator_locals.ts") @@ -893,6 +895,8 @@ tslang_add_test(NAME test-jit-00-string-length-resize COMMAND test-runner -jit " tslang_add_test(NAME test-jit-00-array-length-grow-zeroed COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_grow_zeroed.ts") tslang_add_test(NAME test-jit-00-rest-args-pack-owned COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00rest_args_pack_owned.ts") tslang_add_test(NAME test-jit-00-switch-char-string-cases COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00switch_char_string_cases.ts") +tslang_add_test(NAME test-jit-00-generic-class-constructor-inference COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generic_class_constructor_inference.ts") +tslang_add_test(NAME test-jit-00-generator-done-value-zero COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00generator_done_value_zero.ts") tslang_add_test(NAME test-jit-00-owned-generators COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generators.ts") tslang_add_test(NAME test-jit-00-owned-globals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_globals.ts") tslang_add_test(NAME test-jit-00-owned-generator-locals COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00owned_generator_locals.ts") @@ -1706,7 +1710,9 @@ set(TSLANG_CORPUS 00generator5.ts 00generator6.ts 00generator7.ts + 00generator_done_value_zero.ts 00generic_arguments_name_conflict.ts + 00generic_class_constructor_inference.ts 00global_const_object_method.ts 00globals.ts 00globals2.ts diff --git a/tslang/test/tester/tests/00generator_done_value_zero.ts b/tslang/test/tester/tests/00generator_done_value_zero.ts new file mode 100644 index 000000000..c5e32df5c --- /dev/null +++ b/tslang/test/tester/tests/00generator_done_value_zero.ts @@ -0,0 +1,22 @@ +// A finished generator's `{ value: undefined, done: true }`, when `value` is a type that cannot +// hold undefined, is zero. It was LLVM undef: the value read back was whatever the register held +// (the last yielded 1 here, garbage in a Set's `values().drop(4).next()`). +function* one() { + yield 1; +} + +function main() { + const it = one(); + const first = it.next(); + assert(first.value == 1 && !first.done); + + const finished = it.next(); + assert(finished.done); + assert(finished.value == 0); + + const again = it.next(); + assert(again.done); + assert(again.value == 0); + + print("done."); +} diff --git a/tslang/test/tester/tests/00generic_class_constructor_inference.ts b/tslang/test/tester/tests/00generic_class_constructor_inference.ts new file mode 100644 index 000000000..7e577f722 --- /dev/null +++ b/tslang/test/tester/tests/00generic_class_constructor_inference.ts @@ -0,0 +1,96 @@ +// A generic class written without type arguments takes them from its constructor's arguments, as +// a generic function's call does. `new Set([2, 4, 6, 8])` was Set (its default), and a class +// without defaults was "missing type arguments". +class Box { + first: V; + count: number; + constructor(values: V[]) { + this.first = values[0]; + this.count = values.length; + } +} + +class Defaulted { + value: V; + constructor(value?: V) { + this.value = value; + } +} + +class Pairs { + firstKey: K; + firstValue: V; + constructor(entries?: [K, V][]) { + this.firstKey = entries[0][0]; + this.firstValue = entries[0][1]; + } +} + +class Tagged { + tag: Tag; + constructor(public item: T, tag?: Tag) { + this.tag = tag; + } +} + +class Applied { + result: T; + constructor(start: T, step: (x: T) => T) { + this.result = step(start); + } +} + +function boxOf(items: T[]) { + return new Box(items); +} + +// the same inference a function's call makes: an unnamed tuple's fields are matched by position +// (both took the first element's type, and this did not compile) +function firstValue(entries: [K, V][]): V { + return entries[0][1]; +} + +function main() { + // no default: inferred, or it would not compile + const numbers = new Box([2, 4, 6, 8]); + assert(numbers.first + 1 == 3); + assert(numbers.count == 4); + + const strings = new Box(["a", "b"]); + assert(strings.first + "!" == "a!"); + + // with a default: inferred, not the default + const seven = new Defaulted(7); + assert(seven.value * 2 == 14); + + // no arguments: the default, as before + const nothing = new Defaulted(); + assert(nothing != null); + + // the declared type wins over the arguments + const declared: Defaulted = new Defaulted(3); + assert(declared.value + 0.5 == 3.5); + + // both parameters from a tuple element type + const pairs = new Pairs([["one", 1], ["two", 2]]); + assert(pairs.firstKey + "?" == "one?"); + assert(pairs.firstValue * 10 == 10); + + // the second parameter is not reached and keeps its default + const tagged = new Tagged(5); + assert(tagged.item * 3 == 15); + const taggedToo = new Tagged(5, "five"); + assert(taggedToo.tag == "five"); + + // a callback's parameters come from the constructor's: T is taken from `start` alone + const applied = new Applied(20, (x) => x + 1); + assert(applied.result == 21); + + // inside a generic function: T is known once boxOf is specialized + const boxed = boxOf([1.5, 2.5]); + assert(boxed.first * 2 == 3); + + assert(firstValue([["one", 1], ["two", 2]]) * 10 == 10); + + print("done."); +}