Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions tslang/include/TypeScript/LowerToLLVM/LLVMCodeHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<LLVM::ZeroOp>(loc, llvmType).getResult()
: rewriter.create<mlir_ts::UndefOp>(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<LLVM::ZeroOp>(loc, llvmType).getResult();

tupleVal = rewriter.create<LLVM::InsertValueOp>(loc, tupleVal, itemValue, MLIRHelper::getStructIndex(rewriter, position++));
}
Expand Down
23 changes: 7 additions & 16 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1515,22 +1515,13 @@ class UndefOpLowering : public TsLlvmPattern<mlir_ts::UndefOp>

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<LLVM::ZeroOp>(op, tch.convertType(op.getType()));
return success();
}
}

rewriter.replaceOpWithNewOp<LLVM::UndefOp>(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<si32>'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<LLVM::ZeroOp>(op, tch.convertType(op.getType()));
return success();
}
};
Expand Down
31 changes: 31 additions & 0 deletions tslang/lib/TypeScript/MLIRGenExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<number> = new Set([1, 2])` is Set<number>.
if (newExpression->typeArguments.size() == 0 && newExpression->arguments.size() > 0)
{
if (auto classType = dyn_cast<mlir_ts::ClassType>(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<mlir_ts::ClassRefOp>(
location, inferredClassType,
mlir::FlatSymbolRefAttr::get(builder.getContext(), inferredClassType.getName().getValue()));
}
}
}
}

// `const m: Map<string, number[]> = 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<K = any, V = any>`) made Map<any, any> otherwise, a
Expand Down
121 changes: 121 additions & 0 deletions tslang/lib/TypeScript/MLIRGenGenerics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<si32>).
// Nothing did: the class fell back to its defaults (`Set<V = any>` made Set<any>) 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<Expression> 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<ConstructorDeclaration>();
}
}

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<mlir::Type> 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<mlir::Type> 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<mlir::Type> 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<mlir_ts::ClassType>(specType);
}

std::pair<mlir::LogicalResult, mlir::Type> MLIRGenImpl::instantiateSpecializedInterfaceType(
mlir::Location location, mlir_ts::InterfaceType genericInterfaceType, NodeArray<TypeNode> typeArguments,
const GenContext &genContext, bool allowNamedGenerics)
Expand Down
13 changes: 11 additions & 2 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1210,9 +1210,13 @@ class MLIRGenImpl
bool tryInferTupleFields(mlir::Location location, mlir_ts::TupleType tempTuple, T typeTuple,
StringMap<mlir::Type> &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<int>(position) : -1);
if (index >= 0)
{
inferType(location, tempFieldInfo.type, typeTuple.getFieldInfo(index).type, results, genContext);
Expand Down Expand Up @@ -1294,6 +1298,11 @@ class MLIRGenImpl
const GenContext &genContext,
bool allowNamedGenerics = false);

mlir_ts::ClassType inferClassTypeFromConstructorArguments(mlir::Location location,
GenericClassInfo::TypePtr genericClassInfo,
NodeArray<Expression> arguments,
const GenContext &genContext);

std::pair<mlir::LogicalResult, mlir::Type> instantiateSpecializedInterfaceType(
mlir::Location location, mlir_ts::InterfaceType genericInterfaceType, NodeArray<TypeNode> typeArguments,
const GenContext &genContext, bool allowNamedGenerics = false);
Expand Down
6 changes: 6 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions tslang/test/tester/tests/00generator_done_value_zero.ts
Original file line number Diff line number Diff line change
@@ -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<si32>'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.");
}
96 changes: 96 additions & 0 deletions tslang/test/tester/tests/00generic_class_constructor_inference.ts
Original file line number Diff line number Diff line change
@@ -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<any> (its default), and a class
// without defaults was "missing type arguments".
class Box<V> {
first: V;
count: number;
constructor(values: V[]) {
this.first = values[0];
this.count = values.length;
}
}

class Defaulted<V = any> {
value: V;
constructor(value?: V) {
this.value = value;
}
}

class Pairs<K = any, V = any> {
firstKey: K;
firstValue: V;
constructor(entries?: [K, V][]) {
this.firstKey = entries[0][0];
this.firstValue = entries[0][1];
}
}

class Tagged<T, Tag = string> {
tag: Tag;
constructor(public item: T, tag?: Tag) {
this.tag = tag;
}
}

class Applied<T> {
result: T;
constructor(start: T, step: (x: T) => T) {
this.result = step(start);
}
}

function boxOf<T>(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<K, V>(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<number> = 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.");
}
Loading