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
8 changes: 8 additions & 0 deletions tslang/include/TypeScript/Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@
// stores its `v?: T` as optional<T> - {T, flag}, value first - and its slot carries this bit on top
// of the offset: an access then reads the flag (false is undefined), and a write sets it.
#define INTERFACE_OPTIONAL_STORAGE_TAG(sizeBits) (1LL << ((sizeBits) - 2))
// A function-typed interface field (`fn: () => int`) a class fills with a method (`fn(): int`) has
// no storage in the object: its slot is instead the address of a global cell holding the method,
// plus this bit. An access reads the cell (InterfaceSymbolRefOpLowering), then binds `this` as for
// any function-typed field. The bit is free in every other function-typed slot: an offset of a
// pointer-sized field (or of optional<function>, which starts with one) is pointer-aligned, and so
// is the cell; -1 (absent) is tested before it. A write through such a field changes the cell,
// so it changes the method for every instance of the class seen through that interface.
#define INTERFACE_METHOD_CELL_TAG 1
// on an InterfaceSymbolRefOp of a `v?: T` field that is stored to: true sets the flag of a tagged slot's
// field, false (a store of undefined) clears it
#define INTERFACE_OPTIONAL_WRITE_ATTR "__optional_write"
Expand Down
35 changes: 33 additions & 2 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6475,6 +6475,30 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern<mlir_ts::InterfaceSym
return typedPtr;
};

// a function-typed field's slot tagged INTERFACE_METHOD_CELL_TAG: a class fills the field with a
// method, and the slot is the address of the cell holding it. Tested ahead of
// INTERFACE_OPTIONAL_STORAGE_TAG, which a cell's address can carry on a 32-bit target.
auto fieldRefType = dyn_cast<mlir_ts::RefType>(interfaceSymbolRefOp.getType());
auto isFunctionField = fieldRefType && isa<mlir_ts::FunctionType>(fieldRefType.getElementType());
auto orMethodCell = [&](std::function<mlir::Value(OpBuilder &, Location)> offsetAddrFunc) {
return [&, offsetAddrFunc](OpBuilder &builder, Location location) -> mlir::Value {
auto intPtrType = rewriter.getIntegerType(tsLlvmContext->compileOptions.sizeBits());
auto slotValue = rewriter.create<LLVM::PtrToIntOp>(loc, intPtrType, methodOrFieldPtr);
auto tag = rewriter.create<LLVM::ConstantOp>(
loc, intPtrType, rewriter.getIntegerAttr(intPtrType, INTERFACE_METHOD_CELL_TAG));
auto tagBit = rewriter.create<LLVM::AndOp>(loc, intPtrType, slotValue, tag);
auto zero = rewriter.create<LLVM::ConstantOp>(loc, intPtrType, rewriter.getIntegerAttr(intPtrType, 0));
auto isMethodCell = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, tagBit, zero);
auto methodCellAddrFunc = [&](OpBuilder &builder, Location location) -> mlir::Value {
auto cellInt = rewriter.create<LLVM::SubOp>(loc, intPtrType, slotValue, tag);
return rewriter.create<LLVM::IntToPtrOp>(loc, fieldLLVMTypeRef, cellInt.getResult());
};

return clh.conditionalExpressionLowering(loc, fieldLLVMTypeRef, isMethodCell, methodCellAddrFunc,
offsetAddrFunc);
};
};

mlir::Value fieldAddr;
if (isOptional)
{
Expand Down Expand Up @@ -6530,10 +6554,17 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern<mlir_ts::InterfaceSym
storedAsOptionalAddrFunc, calcFieldTotalAddrFunc);
};

auto result =
clh.conditionalExpressionLowering(loc, fieldLLVMTypeRef, condVal, nullAddrFunc, presentAddrFunc);
auto presentOrMethodCellAddrFunc = orMethodCell(presentAddrFunc);
auto result = clh.conditionalExpressionLowering(
loc, fieldLLVMTypeRef, condVal, nullAddrFunc,
isFunctionField ? mlir::function_ref<mlir::Value(OpBuilder &, Location)>(presentOrMethodCellAddrFunc)
: mlir::function_ref<mlir::Value(OpBuilder &, Location)>(presentAddrFunc));
fieldAddr = result;
}
else if (isFunctionField)
{
fieldAddr = orMethodCell(calcFieldTotalAddrFunc)(rewriter, loc);
}
else
{
auto typedPtr = calcFieldTotalAddrFunc(rewriter, loc);
Expand Down
199 changes: 171 additions & 28 deletions tslang/lib/TypeScript/MLIRGenClasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2352,11 +2352,87 @@ genContext);
llvm::SmallVector<VirtualMethodOrFieldInfo> virtualTable;
// the fields stored as optional<T> for an interface's `v?: T`: their slots are tagged
llvm::SmallVector<mlir::Attribute> optionalStorageFields;
// the interface's function-typed fields (`fn: () => int`) the class implements with a method
// (`fn(): int`): their slots point at a cell holding the method (see INTERFACE_METHOD_CELL_TAG)
llvm::SmallVector<std::pair<mlir::Attribute, MethodInfo>> methodsForFields;
auto result = newInterfacePtr->getVirtualTable(
virtualTable,
[&](mlir::Attribute id, mlir::Type fieldType, bool isConditional) -> std::pair<mlir_ts::FieldInfo, mlir::LogicalResult> {
auto found = false;
auto foundField = newClassPtr->findField(id, found);
if (!found && isa<mlir_ts::FunctionType>(fieldType))
{
auto fieldName = dyn_cast<mlir::StringAttr>(id);
auto *foundMethodPtr = fieldName ? newClassPtr->findMethod(fieldName.getValue()) : nullptr;
if (foundMethodPtr && !foundMethodPtr->isStatic)
{
// the method's first input is its `this`, the field's type has none
auto match = mth.TestFunctionTypesMatchWithObjectMethods(
location, foundMethodPtr->funcType, mlir::cast<mlir_ts::FunctionType>(fieldType), 1, 0);
if (match.result != MatchResultType::Match)
{
emitError(location) << "method signature not matching '" << fieldName.getValue() << ":"
<< to_print(fieldType) << "' for interface '" << newInterfacePtr->fullName
<< "' in class '" << newClassPtr->fullName << "'."
<< " Found method: " << fieldName.getValue() << ":"
<< to_print(foundMethodPtr->funcType);
return {emptyFieldInfo, mlir::failure()};
}

// a field reached twice through a diamond `extends` has two slots and one cell
if (llvm::none_of(methodsForFields, [&](auto &methodForField) { return methodForField.first == id; }))
{
methodsForFields.push_back({id, *foundMethodPtr});
}

return {mlir_ts::FieldInfo{id, fieldType, false, mlir_ts::AccessLevel::Public}, mlir::success()};
}
}

// a class field holding a function (`fn: () => int = ...`) stores a hybrid function -
// {function, `this` or null} - or, unannotated (`fn = () => 7`), a plain one: neither can be handed out
// as a function taking the object as its `this`. A thunk calling `this.fn(...)` fills the
// slot as a method does; it reads the field at each call, so a field assigned later is seen.
// Not an optional field (`fn?: () => int`): the cell is shared, so the slot would read as present
// on every instance, set or not; that stays "field type not matching" below.
if (found && isa<mlir_ts::FunctionType>(fieldType) && fieldType != foundField.type
&& !isa<mlir_ts::OptionalType>(foundField.type) && mth.isAnyFunctionType(foundField.type))
{
auto fieldName = dyn_cast<mlir::StringAttr>(id);
auto match = mth.TestFunctionTypesMatchWithObjectMethods(location, foundField.type, fieldType);
if (!fieldName || match.result != MatchResultType::Match)
{
emitError(location) << "field type not matching for " << id << ":" << to_print(fieldType)
<< " for interface '" << newInterfacePtr->fullName << "' in class '"
<< newClassPtr->fullName << "'. Found field: " << id << ":"
<< to_print(foundField.type);
return {emptyFieldInfo, mlir::failure()};
}

// a field reached twice through a diamond `extends` has two slots and one cell
if (llvm::none_of(methodsForFields, [&](auto &methodForField) { return methodForField.first == id; }))
{
auto funcType = mlir::cast<mlir_ts::FunctionType>(fieldType);
auto thunkName = concat(interfaceVTableNameForClass(newClassPtr, newInterfacePtr),
fieldName.getValue(), "thunk");
if (mlir::failed(generateSynthMethodToCallField(location, newClassPtr, fieldName.getValue(),
thunkName, funcType, genContext)))
{
return {emptyFieldInfo, mlir::failure()};
}

MethodInfo thunkInfo{};
thunkInfo.name = thunkName.str();
thunkInfo.funcType = funcType;
thunkInfo.funcName = thunkName.str();
thunkInfo.virtualIndex = -1;
thunkInfo.accessLevel = mlir_ts::AccessLevel::Public;
methodsForFields.push_back({id, thunkInfo});
}

return {mlir_ts::FieldInfo{id, fieldType, false, mlir_ts::AccessLevel::Public}, mlir::success()};
}

// a class's `v?: T` is stored as optional<T>; the interface's `v?: T` reads it by its flag
// (a tagged slot, see isStoredAsOptional below)
auto optionalStorage = found && isConditional && isa<mlir_ts::OptionalType>(foundField.type)
Expand Down Expand Up @@ -2475,8 +2551,73 @@ genContext);
return owner && owner->isDynamicImport;
});

// the symbol of a method implementing an interface slot - resolved at runtime when the method is owned
// by a dynamically imported class (`location` is then UnknownLoc, see needsRuntimeSymbolResolution)
auto methodSymbolValue = [&](mlir::Location location, MethodInfo &methodInfo, const GenContext &genContext) -> mlir::Value {
auto owner = findMethodOwner(newClassPtr, methodInfo.funcName);
if (owner && owner->isDynamicImport)
{
// `location` is already UnknownLoc here (see
// needsRuntimeSymbolResolution above, computed from this same
// owner->isDynamicImport condition) - the resulting
// SearchForAddressOfSymbolOp forces this whole global's
// initializer into a synthesized __cctor function via
// GlobalOpLowering, and a "live" (main-scoped) location on any op
// living there would attach the wrong DISubprogram to it.
auto symbolNameValue = V(mlirGenStringValue(location,
methodInfo.dllName.empty() ? methodInfo.funcName : methodInfo.dllName, true));
auto referenceToSymbolOpaque = builder.create<mlir_ts::SearchForAddressOfSymbolOp>(
location, getOpaqueType(), symbolNameValue);
auto castResult = cast(location, methodInfo.funcType, referenceToSymbolOpaque, genContext);
if (castResult.failed_or_no_value())
{
return mlir::Value();
}

return V(castResult);
}

return builder.create<mlir_ts::SymbolRefOp>(
location, methodInfo.funcType,
mlir::FlatSymbolRefAttr::get(builder.getContext(), methodInfo.funcName));
};

// register global
auto fullClassInterfaceVTableFieldName = interfaceVTableNameForClass(newClassPtr, newInterfacePtr);

// one cell per method filling a function-typed field: the field's slot holds its address
// (tagged), since the object itself has no field holding the method. The variable keeps the
// name it is registered with, so the name is allocated like the vtable's own.
auto methodCellName = [&](mlir::Attribute id) {
return concat(fullClassInterfaceVTableFieldName, mlir::cast<mlir::StringAttr>(id).getValue(), "method");
};

for (auto &[id, methodInfo] : methodsForFields)
{
auto owner = findMethodOwner(newClassPtr, methodInfo.funcName);
auto cellType = registerVariable(
location, methodCellName(id), true, VariableType::Var,
[&](mlir::Location location, const GenContext &genContext) {
if (owner && owner->isDynamicImport)
{
location = mlir::UnknownLoc::get(builder.getContext());
}

auto methodValue = methodSymbolValue(location, methodInfo, genContext);
if (!methodValue)
{
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

return TypeValueInitType{methodInfo.funcType, methodValue, TypeProvided::Yes};
},
genContext);
if (!cellType)
{
return mlir::failure();
}
}

auto registeredType = registerVariable(
location, fullClassInterfaceVTableFieldName, true, VariableType::Var,
[&](mlir::Location location, const GenContext &genContext) {
Expand Down Expand Up @@ -2529,6 +2670,33 @@ genContext);
location, virtTuple, castedPtr, vtableValue,
MLIRHelper::getStructIndex(builder, fieldIndex));
}
else if (methodOrField.isField && llvm::any_of(methodsForFields, [&](auto &methodForField) {
return methodForField.first == methodOrField.fieldInfo.id;
}))
{
// a method filling a function-typed field: the slot is the address of its cell plus
// INTERFACE_METHOD_CELL_TAG, so an access reads the cell instead of `this` + offset
// (InterfaceSymbolRefOpLowering)
auto cellRef = resolveFullNameIdentifier(location, methodCellName(methodOrField.fieldInfo.id), true, genContext);
if (!cellRef)
{
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

auto ptrIntType = builder.getIntegerType(compileOptions.sizeBits());
auto cellAddress = builder.create<mlir_ts::CastOp>(location, ptrIntType, cellRef);
auto tagValue = builder.create<mlir_ts::ConstantOp>(
location, ptrIntType, builder.getIntegerAttr(ptrIntType, INTERFACE_METHOD_CELL_TAG));
auto taggedValue = builder.create<mlir_ts::ArithmeticBinaryOp>(
location, ptrIntType, builder.getI32IntegerAttr((int)SyntaxKind::PlusToken), cellAddress,
tagValue);
auto slotValue = builder.create<mlir_ts::CastOp>(
location, mlir_ts::RefType::get(methodOrField.fieldInfo.type), taggedValue);

vtableValue = builder.create<mlir_ts::InsertPropertyOp>(
location, virtTuple, slotValue, vtableValue,
MLIRHelper::getStructIndex(builder, fieldIndex));
}
else if (methodOrField.isField)
{
auto nullObj = builder.create<mlir_ts::NullOp>(location, getNullType());
Expand Down Expand Up @@ -2581,35 +2749,10 @@ genContext);
// "not a link-time constant, resolve at runtime" situation
// mlirGenClassVirtualTableDefinition already handles for its own
// (non-interface) vtable slots, just not previously mirrored here.
auto owner = findMethodOwner(newClassPtr, methodOrField.methodInfo.funcName);
mlir::Value methodValueRef;
if (owner && owner->isDynamicImport)
auto methodValueRef = methodSymbolValue(location, methodOrField.methodInfo, genContext);
if (!methodValueRef)
{
// `location` is already UnknownLoc here (see
// needsRuntimeSymbolResolution above, computed from this same
// owner->isDynamicImport condition) - the resulting
// SearchForAddressOfSymbolOp forces this whole global's
// initializer into a synthesized __cctor function via
// GlobalOpLowering, and a "live" (main-scoped) location on any op
// living there would attach the wrong DISubprogram to it.
auto symbolNameValue = V(mlirGenStringValue(location,
methodOrField.methodInfo.dllName.empty() ? methodOrField.methodInfo.funcName : methodOrField.methodInfo.dllName, true));
auto referenceToSymbolOpaque = builder.create<mlir_ts::SearchForAddressOfSymbolOp>(
location, getOpaqueType(), symbolNameValue);
auto castResult = cast(location, methodOrField.methodInfo.funcType, referenceToSymbolOpaque, genContext);
if (castResult.failed_or_no_value())
{
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

methodValueRef = V(castResult);
}
else
{
methodValueRef = builder.create<mlir_ts::SymbolRefOp>(
location, methodOrField.methodInfo.funcType,
mlir::FlatSymbolRefAttr::get(builder.getContext(),
methodOrField.methodInfo.funcName));
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

vtableValue = builder.create<mlir_ts::InsertPropertyOp>(
Expand Down
Loading
Loading