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
7 changes: 7 additions & 0 deletions tslang/include/TypeScript/Defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@
// INTERFACE_VTABLE_HEADER_SLOTS.
#define INTERFACE_VTABLE_INSTANCEOF_SLOT 0
#define INTERFACE_VTABLE_HEADER_SLOTS 1
// An interface `v?: T` field's slot is the field's offset from `this`, or -1 when absent. A class
// 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))
// 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"
#define INSTANCEOF_NONE_NAME ".instanceOf.none"
#define MAIN_ENTRY_NAME "main"
#define TS_NEST_ATTRIBUTE "ts.nest"
Expand Down
4 changes: 3 additions & 1 deletion tslang/include/TypeScript/TypeScriptOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,10 @@ def BoundIndirectIndexAccessorOp : TypeScript_Op<"BoundIndirectIndexAccessor",
let hasCanonicalizer = 1;
}

// reads memory: the vtable slot, and for a `v?: T` field stored as optional<T> its flag (see
// INTERFACE_OPTIONAL_STORAGE_TAG), so two accesses with a write between them are not one
def InterfaceSymbolRefOp : TypeScript_Op<"InterfaceSymbolRef",
[Pure]> {
[MemoryEffects<[MemRead]>]> {
let summary = "symbol reference as to interface member";

let arguments = (ins TypeScript_Interface:$interfaceVal, I32Attr:$index, AnyAttr:$identifier, OptionalAttr<BoolAttr>:$optional);
Expand Down
44 changes: 42 additions & 2 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5768,14 +5768,54 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern<mlir_ts::InterfaceSym
};

// Both sides take the target width from compileOptions, not from the type converter's data layout, which is not yet target-derived.
auto intPtrType = rewriter.getIntegerType(tsLlvmContext->compileOptions.sizeBits());
auto sizeBits = tsLlvmContext->compileOptions.sizeBits();
auto intPtrType = rewriter.getIntegerType(sizeBits);
auto negative1 = rewriter.create<LLVM::ConstantOp>(loc, intPtrType, rewriter.getIntegerAttr(intPtrType, -1));
auto methodOrFieldIntPtrValue = rewriter.create<LLVM::PtrToIntOp>(loc, intPtrType, methodOrFieldPtr);
auto condVal =
rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, methodOrFieldIntPtrValue, negative1);

// a slot tagged INTERFACE_OPTIONAL_STORAGE_TAG: the field is stored as optional<T> (a class's `v?: T`),
// present only when its flag is set. A write through the interface sets the flag (clears it for
// `= undefined`), marked by MLIRGen with INTERFACE_OPTIONAL_WRITE_ATTR.
auto writeAttr = interfaceSymbolRefOp->getAttrOfType<mlir::BoolAttr>(INTERFACE_OPTIONAL_WRITE_ATTR);
auto storedAsOptionalAddrFunc = [&](OpBuilder &builder, Location location) -> mlir::Value {
auto tag = rewriter.create<LLVM::ConstantOp>(
loc, intPtrType, rewriter.getIntegerAttr(intPtrType, INTERFACE_OPTIONAL_STORAGE_TAG(sizeBits)));
auto offset = rewriter.create<LLVM::SubOp>(loc, intPtrType, methodOrFieldIntPtrValue, tag);
auto thisInt = rewriter.create<LLVM::PtrToIntOp>(loc, intPtrType, thisVal);
auto addrInt = rewriter.create<LLVM::AddOp>(loc, intPtrType, thisInt, offset);
mlir::Value addr = rewriter.create<LLVM::IntToPtrOp>(loc, fieldLLVMTypeRef, addrInt.getResult());

auto valueType = mlir::cast<mlir_ts::RefType>(interfaceSymbolRefOp.getType()).getElementType();
auto optionalLLVMType = tch.convertType(mlir_ts::OptionalType::get(valueType));
auto flagPtr = rewriter.create<LLVM::GEPOp>(loc, th.getPtrType(), optionalLLVMType, addr,
ArrayRef<LLVM::GEPArg>{0, OPTIONAL_HASVALUE_INDEX});
if (writeAttr)
{
auto flagValue = rewriter.create<LLVM::ConstantOp>(loc, rewriter.getI1Type(),
rewriter.getBoolAttr(writeAttr.getValue()));
rewriter.create<LLVM::StoreOp>(loc, flagValue, flagPtr);
return addr;
}

auto hasValue = rewriter.create<LLVM::LoadOp>(loc, rewriter.getI1Type(), flagPtr);
auto nullAddr = rewriter.create<LLVM::ZeroOp>(loc, fieldLLVMTypeRef);
return rewriter.create<LLVM::SelectOp>(loc, hasValue, addr, nullAddr);
};

auto presentAddrFunc = [&](OpBuilder &builder, Location location) -> mlir::Value {
auto tag = rewriter.create<LLVM::ConstantOp>(
loc, intPtrType, rewriter.getIntegerAttr(intPtrType, INTERFACE_OPTIONAL_STORAGE_TAG(sizeBits)));
auto tagBit = rewriter.create<LLVM::AndOp>(loc, intPtrType, methodOrFieldIntPtrValue, tag);
auto zero = rewriter.create<LLVM::ConstantOp>(loc, intPtrType, rewriter.getIntegerAttr(intPtrType, 0));
auto isStoredAsOptional = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, tagBit, zero);
return clh.conditionalExpressionLowering(loc, fieldLLVMTypeRef, isStoredAsOptional,
storedAsOptionalAddrFunc, calcFieldTotalAddrFunc);
};

auto result =
clh.conditionalExpressionLowering(loc, fieldLLVMTypeRef, condVal, nullAddrFunc, calcFieldTotalAddrFunc);
clh.conditionalExpressionLowering(loc, fieldLLVMTypeRef, condVal, nullAddrFunc, presentAddrFunc);
fieldAddr = result;
}
else
Expand Down
50 changes: 46 additions & 4 deletions tslang/lib/TypeScript/MLIRGenClasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -813,9 +813,16 @@ namespace mlirgen
type = mth.wideStorageType(type);
}

LLVM_DEBUG(dbgs() << "\n!! class field: " << fieldId << " type: " << type << " access level: " << accessLevel);

auto hasType = !!propertyDeclaration->type;

// `v?: T` is `v: T | undefined`, as in a type literal: unassigned it reads as undefined, not as T's zero.
// An interface's `v?: T` member reads it by its flag (see mlirGenClassVirtualTableDefinitionForInterface).
if (propertyDeclaration->questionToken && type && !mth.isNoneType(type))
{
type = getUnionType(location, type, getUndefinedType());
}

LLVM_DEBUG(dbgs() << "\n!! class field: " << fieldId << " type: " << type << " access level: " << accessLevel);
if (mth.isNoneType(type))
{
if (hasType)
Expand Down Expand Up @@ -1019,12 +1026,20 @@ namespace mlirgen

auto [type, init, typeProvided] = evaluateTypeAndInit(parameter, genContext);

LLVM_DEBUG(dbgs() << "\n+++ class auto-gen field: " << fieldId << " type: " << type << "");
if (mth.isNoneType(type))
{
return mlir::failure();
}

// `constructor(public m?: T)` is a field `m?: T`: T | undefined, as a declared one
// (mlirGenClassDataFieldMember) - `new Error().message` is undefined
if (parameter->questionToken)
{
type = getUnionType(location, type, getUndefinedType());
}

LLVM_DEBUG(dbgs() << "\n+++ class auto-gen field: " << fieldId << " type: " << type << "");

fieldInfos.push_back(
{
fieldId,
Expand Down Expand Up @@ -1607,12 +1622,18 @@ genContext);
auto classStorageType = mlir::cast<mlir_ts::ClassStorageType>(newClassPtr->classType.getStorageType());

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;
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 || fieldType != foundField.type)
// 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)
&& mlir::cast<mlir_ts::OptionalType>(foundField.type).getElementType() == fieldType;
if (!found || (fieldType != foundField.type && !optionalStorage))
{
if (!found && !isConditional || found)
{
Expand All @@ -1626,6 +1647,11 @@ genContext);
return {emptyFieldInfo, mlir::success()};
}

if (optionalStorage)
{
optionalStorageFields.push_back(id);
}

return {foundField, mlir::success()};
},
[&](std::string name, mlir_ts::FunctionType funcType, bool isConditional, int interfacePosIndex) -> std::pair<MethodInfo &, mlir::LogicalResult> {
Expand Down Expand Up @@ -1798,6 +1824,22 @@ genContext);
return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::No};
}

// stored as optional<T> - {T, flag}, value first - for an interface's `v?: T`: the slot is
// the offset plus INTERFACE_OPTIONAL_STORAGE_TAG, so an access reads the flag
// (InterfaceSymbolRefOpLowering)
if (llvm::is_contained(optionalStorageFields, methodOrField.fieldInfo.id))
{
auto ptrIntType = builder.getIntegerType(compileOptions.sizeBits());
auto offsetValue = builder.create<mlir_ts::CastOp>(location, ptrIntType, fieldRef);
auto tagValue = builder.create<mlir_ts::ConstantOp>(
location, ptrIntType,
builder.getIntegerAttr(ptrIntType, INTERFACE_OPTIONAL_STORAGE_TAG(compileOptions.sizeBits())));
auto taggedValue = builder.create<mlir_ts::ArithmeticBinaryOp>(
location, ptrIntType, builder.getI32IntegerAttr((int)SyntaxKind::PlusToken), offsetValue,
tagValue);
fieldRef = builder.create<mlir_ts::CastOp>(location, fieldRef.getType(), taggedValue);
}

// insert &(null)->field
vtableValue = builder.create<mlir_ts::InsertPropertyOp>(
location, virtTuple, fieldRef, vtableValue,
Expand Down
21 changes: 18 additions & 3 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5500,6 +5500,21 @@ class MLIRGenImpl

LLVM_DEBUG(llvm::dbgs() << "\n!! Dest type: " << destType << "\n";);

// an interface's `v?: T` field: a store sets its flag where it is stored as optional<T> (a class's
// `v?: T`), and a store of undefined clears it - see INTERFACE_OPTIONAL_STORAGE_TAG
mlir::Value storeReference = loadOp.getReference();
if (auto interfaceSymbolRefOp = storeReference.getDefiningOp<mlir_ts::InterfaceSymbolRefOp>())
{
if (interfaceSymbolRefOp.getOptional().value_or(false) && isa<mlir_ts::RefType>(interfaceSymbolRefOp.getType()))
{
auto storesUndefined = isa<mlir_ts::UndefinedType>(savingValue.getType())
|| (isa<mlir_ts::OptionalType>(savingValue.getType()) && isa_and_nonnull<mlir_ts::OptionalUndefOp>(savingValue.getDefiningOp()));
auto writeOp = mlir::cast<mlir_ts::InterfaceSymbolRefOp>(builder.clone(*interfaceSymbolRefOp));
writeOp->setAttr(INTERFACE_OPTIONAL_WRITE_ATTR, builder.getBoolAttr(!storesUndefined));
storeReference = writeOp.getResult();
}
}

syncSavingValue(destType);
if (!savingValue)
{
Expand All @@ -5512,7 +5527,7 @@ class MLIRGenImpl
// stored back. Without this the release that eventually runs for this storage -
// scope exit for a local, the instance's release routine for a field - would give
// up a reference the assignment never took.
if (isOwningSlot(location, loadOp.getReference()))
if (isOwningSlot(location, storeReference))
{
// `h.item = new C()` arrives already owned (§9.25), so the slot takes that
// reference over instead of adding one. The release still runs either way -
Expand All @@ -5527,12 +5542,12 @@ class MLIRGenImpl
builder.create<mlir_ts::RetainOp>(location, savingValue);
}

builder.create<mlir_ts::ReleaseSlotOp>(location, loadOp.getReference());
builder.create<mlir_ts::ReleaseSlotOp>(location, storeReference);
}

// TODO: when saving const array into variable we need to allocate space and copy array as we need to have
// writable array
auto storeOp = builder.create<mlir_ts::StoreOp>(location, savingValue, loadOp.getReference());
auto storeOp = builder.create<mlir_ts::StoreOp>(location, savingValue, storeReference);
cloneAtomicAttributes(loadOp, storeOp);
}
else if (auto extractPropertyOp = leftExpressionValueBeforeCast.getDefiningOp<mlir_ts::ExtractPropertyOp>())
Expand Down
2 changes: 2 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ tslang_add_test(NAME test-compile-00-generator-return COMMAND test-runner "${PRO
tslang_add_test(NAME test-compile-00-return-inference-undefined COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00return_inference_undefined.ts")
tslang_add_test(NAME test-compile-00-return-falls-off-end COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00return_falls_off_end.ts")
tslang_add_test(NAME test-compile-00-nullish-values COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00nullish_values.ts")
tslang_add_test(NAME test-compile-00-class-optional-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_optional_fields.ts")
tslang_add_test(NAME test-compile-00-signless-int-signed COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00signless_int_signed.ts")
tslang_add_test(NAME test-compile-00-intersection-type COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00intersection_type.ts")
tslang_add_test(NAME test-compile-00-intersection-type-generic COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00intersection_type_generic.ts")
Expand Down Expand Up @@ -880,6 +881,7 @@ tslang_add_test(NAME test-jit-00-generator-return COMMAND test-runner -jit "${PR
tslang_add_test(NAME test-jit-00-return-inference-undefined COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00return_inference_undefined.ts")
tslang_add_test(NAME test-jit-00-return-falls-off-end COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00return_falls_off_end.ts")
tslang_add_test(NAME test-jit-00-nullish-values COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00nullish_values.ts")
tslang_add_test(NAME test-jit-00-class-optional-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_optional_fields.ts")
tslang_add_test(NAME test-jit-00-signless-int-signed COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00signless_int_signed.ts")
tslang_add_test(NAME test-jit-00-union-global-init COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00union_global_init.ts")
tslang_add_test(NAME test-jit-00-intersection-type COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00intersection_type.ts")
Expand Down
117 changes: 117 additions & 0 deletions tslang/test/tester/tests/00class_optional_fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// a class's `v?: T` is `T | undefined`: unassigned it reads as undefined, directly and through an
// interface's `v?: T` (whose slot then reads the field's flag); a write through the interface sets it
interface I {
v?: string;
n?: number;
k: number;
}

class C implements I {
v?: string;
n?: number;
constructor(public k: number) {}
}

class Base {
a = 1;
v?: string;
}

class D extends Base implements I {
k: number = 2;
x?: boolean = true;
}

class G<T> {
g?: T;
}

interface J {
u?: number;
}

class E implements J {
pad = "p";
u?: number;
}

class P {
constructor(public m?: string, public n?: number) {}
}

type L = { u?: number };

function viaInterface(i: I) {
return i.n ?? -1;
}

function sum(j: J) {
let s = 0;
for (let k = 0; k < 3; k++) {
if (j.u === undefined) {
j.u = 10;
}

s += j.u ?? 100;
}

return s;
}

function viaLiteral(l: L) {
return l.u ?? -1;
}

function main() {
const c = new C(1);
assert(c.v === undefined && c.n === undefined, "unassigned");
assert((c.n ?? -1) == -1, "unassigned ??");
assert(viaInterface(c) == -1, "unassigned through an interface");

c.n = 0;
assert(c.n === 0, "assigned");
assert(viaInterface(c) == 0, "zero through an interface");

const i: I = c;
assert(i.v === undefined, "unassigned string through an interface");
i.v = "x";
assert(c.v == "x" && i.v == "x", "written through an interface");
i.n = 5;
assert(c.n == 5 && i.n == 5, "number written through an interface");
i.v = undefined;
assert(c.v === undefined && i.v === undefined, "undefined written through an interface");
c.n = undefined;
assert(i.n === undefined, "undefined written directly");

const d = new D();
const di: I = d;
assert(di.v === undefined, "inherited field");
di.v = "dv";
assert(d.v == "dv" && di.v == "dv", "inherited field written through an interface");
assert(d.x === true, "initialized");
d.x = undefined;
assert(d.x === undefined, "cleared");

const g = new G<number>();
assert(g.g === undefined, "generic");
g.g = 3;
assert(g.g == 3, "generic assigned");

const e = new E();
assert(sum(e) == 30 && e.u == 10, "loop through an interface");

const e2 = new E();
assert(viaLiteral(e2) == -1, "to a type literal");
e2.u = 4;
assert(viaLiteral(e2) == 4, "to a type literal, assigned");

const p = new P();
assert(p.m === undefined && p.n === undefined, "constructor parameter fields");
const p2 = new P("a", 0);
assert(p2.m == "a" && p2.n === 0, "constructor parameter fields, given");

const literal: I = { k: 3 };
assert(literal.v === undefined, "object literal without the field");

print("done.");
}
Loading