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
Original file line number Diff line number Diff line change
Expand Up @@ -3527,9 +3527,18 @@ of a private virtual family.
other than to be read or stored, an object of the literal's type made other than by the literal,
a function-typed store through an open interface, or a class implementer (its slot is a thunk's
cell) keeps the call open. In the language as it is, a function stored into such a field is
always the literal's: `s.f = fn` through the interface is `saving to constant object`, and a
function without `this` (a parameter, a named function) is held through a thunk, which is open
(`own_err_interface_fn_field_unknown`).
always the literal's: `s.f = fn` through the interface stops the program when a literal is behind
it, and a function without `this` (a parameter, a named function) is held through a thunk, which
is open (`own_err_interface_fn_field_unknown`). Through the interface such a store is taken as
unknown (its ref is also tested for null), so it opens every interface field's facts.
- **A class field written through an interface** (`h.f = show`, `h: Holder`, `Walker.f` filling
`Holder.f`): the class's cell names the field's offset (INTERFACE_FUNCTION_FIELD_WRITE_ATTR) and
the store goes to the instance. `collectFieldFamilies` sees no `ts.PropertyRef` at the store; the
family is kept sound by the cell's initializer, whose `ts.PropertyRef` of the field (its offset) is
neither a load nor a store, so the field's family is open whenever such a cell exists - also when
nothing stores through the interface. (Under §58 a class seen through an interface may keep its
field families; that cell then has to count as a store only when a write-marked
`ts.InterfaceSymbolRef` of the field's name exists in the module.)

`own_interface_facts` runs the plan's loop over `sc.ts` (prints 9), a field borrowed across a call
through two class members and an object-literal member, the closed forms of the five §27 negatives
Expand Down
22 changes: 15 additions & 7 deletions tslang/include/TypeScript/Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,18 +207,26 @@
// 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 function-typed interface field cannot be
// written ("saving to constant object"): a read binds `this`, so nothing stores to the cell.
// The cell is {method, flag offset}, the method first: a class's optional field holding a function
// (`fn?: () => int`, called through a thunk) has its flag at `this` + the offset, so a read of the
// interface's optional field (`fn?: () => int`) is absent while the instance's flag is clear; -1
// is always present (a method, or a field that is not optional).
// is the cell; -1 (absent) is tested before it.
// The cell is {method, flag offset, field offset}, the method first: a class's optional field holding a
// function (`fn?: () => int`, called through a thunk) has its flag at `this` + the flag offset, so a read
// of the interface's optional field (`fn?: () => int`) is absent while the instance's flag is clear; -1
// is always present (a method, or a field that is not optional). The field offset is of the class's
// field the thunk calls, when that field is stored as the interface field's function made a hybrid
// (`f: (v: T) => void` in both): a write through the interface (`h.f = g`) stores there. It is -1
// where nothing can be written - a method, or a field of another type - and the write then stops the
// program (INTERFACE_FUNCTION_FIELD_WRITE_ATTR).
// An object field holding a function the interface can't call with the object as its `this` (a
// closure) is filled the same way, with a thunk calling the field; its cell is always present.
// closure) is filled the same way, with a thunk calling the field; its cell is always present and
// is not written through.
#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"
// on an InterfaceSymbolRefOp of a function-typed field that is stored to (its result is then a ref to
// the field's function as a hybrid): the address of the class field a tagged slot's cell names, or
// null where the slot can't be written (a method's cell, or an object literal's field)
#define INTERFACE_FUNCTION_FIELD_WRITE_ATTR "__function_field_write"
#define INSTANCEOF_NONE_NAME ".instanceOf.none"
#define MAIN_ENTRY_NAME "main"
#define TS_NEST_ATTRIBUTE "ts.nest"
Expand Down
10 changes: 10 additions & 0 deletions tslang/include/TypeScript/MLIRLogic/MLIRTypeHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,16 @@ class MLIRTypeHelper
return getFunctionTypeWithThisType(funcType, mlir_ts::OpaqueType::get(context), replace);
}

// an interface's function-typed field (`f: (v: T) => void`, a function taking the object as an opaque
// `this`) as a class field of that type stores it: a hybrid of the function without the `this`
mlir_ts::HybridFunctionType getInterfaceFieldFunctionAsHybrid(mlir_ts::FunctionType fieldType)
{
auto offset = fieldType.getNumInputs() > 0 && isa<mlir_ts::OpaqueType>(fieldType.getInput(0)) ? 1 : 0;
auto funcType = mlir_ts::FunctionType::get(context, fieldType.getInputs().slice(offset), fieldType.getResults(),
fieldType.isVarArg());
return mlir_ts::HybridFunctionType::get(context, funcType);
}

mlir_ts::FunctionType getFunctionTypeAddingFirstArgType(mlir_ts::FunctionType funcType, mlir::Type firstArgType)
{
mlir::SmallVector<mlir::Type> funcArgTypes(funcType.getInputs().begin(), funcType.getInputs().end());
Expand Down
47 changes: 43 additions & 4 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6802,6 +6802,45 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern<mlir_ts::InterfaceSym
auto methodOrFieldPtr = rewriter.create<mlir_ts::VTableOffsetRefOp>(
loc, th.getPtrType(), vtable, interfaceSymbolRefOp.getIndex() + INTERFACE_VTABLE_HEADER_SLOTS);

// a write of a function-typed field (`h.f = g`, not optional): the class field a tagged slot's cell
// names, or null where nothing can be written - a method's cell (field offset -1), or an untagged
// slot, an object literal's field holding a function that takes the object as its `this`
if (interfaceSymbolRefOp->hasAttr(INTERFACE_FUNCTION_FIELD_WRITE_ATTR))
{
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 classFieldAddrFunc = [&](OpBuilder &builder, Location location) -> mlir::Value {
// the cell is {method, flag offset, field offset}
auto cellInt = rewriter.create<LLVM::SubOp>(loc, intPtrType, slotValue, tag);
auto cellAddr = rewriter.create<LLVM::IntToPtrOp>(loc, th.getPtrType(), cellInt.getResult());
auto cellLLVMType = LLVM::LLVMStructType::getLiteral(
rewriter.getContext(), {th.getPtrType(), intPtrType, intPtrType}, false);
auto fieldOffsetPtr = rewriter.create<LLVM::GEPOp>(loc, th.getPtrType(), cellLLVMType, cellAddr,
ArrayRef<LLVM::GEPArg>{0, 2});
auto fieldOffset = rewriter.create<LLVM::LoadOp>(loc, intPtrType, fieldOffsetPtr);
auto negative1 = rewriter.create<LLVM::ConstantOp>(loc, intPtrType, rewriter.getIntegerAttr(intPtrType, -1));
auto isWritable = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, fieldOffset, negative1);
auto thisInt = rewriter.create<LLVM::PtrToIntOp>(loc, intPtrType, thisVal);
auto fieldInt = rewriter.create<LLVM::AddOp>(loc, intPtrType, thisInt, fieldOffset);
auto fieldPtr = rewriter.create<LLVM::IntToPtrOp>(loc, fieldLLVMTypeRef, fieldInt.getResult());
auto nullAddr = rewriter.create<LLVM::ZeroOp>(loc, fieldLLVMTypeRef);
return rewriter.create<LLVM::SelectOp>(loc, isWritable, fieldPtr, nullAddr);
};
auto nullAddrFunc = [&](OpBuilder &builder, Location location) -> mlir::Value {
return rewriter.create<LLVM::ZeroOp>(loc, fieldLLVMTypeRef);
};

auto fieldAddr = clh.conditionalExpressionLowering(loc, fieldLLVMTypeRef, isMethodCell, classFieldAddrFunc,
nullAddrFunc);
rewriter.replaceOp(interfaceSymbolRefOp, ValueRange{fieldAddr});
return success();
}

if (auto boundFunc = dyn_cast<mlir_ts::BoundFunctionType>(interfaceSymbolRefOp.getType()))
{
mlir::Value thisValEffective = thisVal;
Expand Down Expand Up @@ -6909,9 +6948,9 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern<mlir_ts::InterfaceSym
return cellAddr;
}

// the cell is {method, flag offset}
auto cellLLVMType =
LLVM::LLVMStructType::getLiteral(rewriter.getContext(), {th.getPtrType(), intPtrType}, false);
// the cell is {method, flag offset, field offset}
auto cellLLVMType = LLVM::LLVMStructType::getLiteral(
rewriter.getContext(), {th.getPtrType(), intPtrType, intPtrType}, false);
auto flagOffsetPtr = rewriter.create<LLVM::GEPOp>(loc, th.getPtrType(), cellLLVMType, cellAddr,
ArrayRef<LLVM::GEPArg>{0, 1});
auto flagOffset = rewriter.create<LLVM::LoadOp>(loc, intPtrType, flagOffsetPtr);
Expand Down Expand Up @@ -6994,7 +7033,7 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern<mlir_ts::InterfaceSym
storedAsOptionalAddrFunc, calcFieldTotalAddrFunc);
};

// a function-typed field is only read (a write is "saving to constant object"), so the
// an optional function-typed field is only read (a write is refused in MLIRGen), so the
// presence check always applies
auto presentOrMethodCellAddrFunc = orMethodCell(presentAddrFunc, true);
auto result = clh.conditionalExpressionLowering(
Expand Down
59 changes: 50 additions & 9 deletions tslang/lib/TypeScript/MLIRGenClasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2578,6 +2578,9 @@ genContext);
// of those, the ones filled by an optional class field (`fn?: () => int`): their cells carry the
// offset of the field's flag, so an access reads the instance's presence
llvm::SmallVector<mlir::Attribute> optionalFieldsForCells;
// and the ones filled by a class field stored as the interface field's function made a hybrid: their
// cells carry the field's offset, so a write through the interface (`h.f = g`) stores to the instance
llvm::SmallVector<mlir::Attribute> writableFieldsForCells;
auto result = newInterfacePtr->getVirtualTable(
virtualTable,
[&](mlir::Attribute id, mlir::Type fieldType, bool isConditional) -> std::pair<mlir_ts::FieldInfo, mlir::LogicalResult> {
Expand Down Expand Up @@ -2656,6 +2659,10 @@ genContext);
{
optionalFieldsForCells.push_back(id);
}
else if (foundField.type == mth.getInterfaceFieldFunctionAsHybrid(funcType))
{
writableFieldsForCells.push_back(id);
}
}

return {mlir_ts::FieldInfo{id, fieldType, false, mlir_ts::AccessLevel::Public}, mlir::success()};
Expand Down Expand Up @@ -2846,14 +2853,17 @@ genContext);
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

// the cell is {method, flag offset}, the method first so the slot is a ref to it. The offset
// is of the flag of an optional class field (`fn?: () => int`), from `this`; -1 is always
// present (a method, or a field that is not optional)
// the cell is {method, flag offset, field offset}, the method first so the slot is a ref to it.
// The flag offset is of the flag of an optional class field (`fn?: () => int`), from `this`; -1
// is always present (a method, or a field that is not optional). The field offset is of the
// class field a write through the interface stores to; -1 is not written.
auto ptrIntType = builder.getIntegerType(compileOptions.sizeBits());
mlir::Value flagOffsetValue;
if (llvm::is_contained(optionalFieldsForCells, id))
{
// &(null)->field, stored as optional<T> - {T, flag}, the flag right after T
auto negative1 = [&]() -> mlir::Value {
return builder.create<mlir_ts::ConstantOp>(location, ptrIntType, builder.getIntegerAttr(ptrIntType, -1));
};

// &(null)->field
auto fieldRefOf = [&]() -> mlir::Value {
MLIRCodeLogic mcl(builder, compileOptions);
auto nullObj = builder.create<mlir_ts::NullOp>(location, getNullType());
auto classNull = cast(location, newClassPtr->classType, nullObj, genContext, true);
Expand All @@ -2864,6 +2874,18 @@ genContext);
emitError(location) << "can't find reference for field: " << id
<< " in interface: " << newInterfacePtr->fullName
<< " for class: " << newClassPtr->fullName;
}

return fieldRef;
};

mlir::Value flagOffsetValue;
if (llvm::is_contained(optionalFieldsForCells, id))
{
// stored as optional<T> - {T, flag}, the flag right after T
auto fieldRef = fieldRefOf();
if (!fieldRef)
{
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

Expand All @@ -2879,21 +2901,40 @@ genContext);
}
else
{
flagOffsetValue =
builder.create<mlir_ts::ConstantOp>(location, ptrIntType, builder.getIntegerAttr(ptrIntType, -1));
flagOffsetValue = negative1();
}

mlir::Value fieldOffsetValue;
if (llvm::is_contained(writableFieldsForCells, id))
{
auto fieldRef = fieldRefOf();
if (!fieldRef)
{
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

fieldOffsetValue = builder.create<mlir_ts::CastOp>(location, ptrIntType, fieldRef);
}
else
{
fieldOffsetValue = negative1();
}

mlir::SmallVector<mlir_ts::FieldInfo> cellFields{
{MLIRHelper::TupleFieldName("method", builder.getContext()), methodInfo.funcType, false,
mlir_ts::AccessLevel::Public},
{MLIRHelper::TupleFieldName("flagOffset", builder.getContext()), ptrIntType, false,
mlir_ts::AccessLevel::Public},
{MLIRHelper::TupleFieldName("fieldOffset", builder.getContext()), ptrIntType, false,
mlir_ts::AccessLevel::Public}};
auto cellTupleType = getTupleType(cellFields);
mlir::Value cellValue = builder.create<mlir_ts::UndefOp>(location, cellTupleType);
cellValue = builder.create<mlir_ts::InsertPropertyOp>(location, cellTupleType, methodValue, cellValue,
MLIRHelper::getStructIndex(builder, 0));
cellValue = builder.create<mlir_ts::InsertPropertyOp>(location, cellTupleType, flagOffsetValue,
cellValue, MLIRHelper::getStructIndex(builder, 1));
cellValue = builder.create<mlir_ts::InsertPropertyOp>(location, cellTupleType, fieldOffsetValue,
cellValue, MLIRHelper::getStructIndex(builder, 2));
return TypeValueInitType{cellTupleType, cellValue, TypeProvided::Yes};
},
genContext);
Expand Down
Loading
Loading