diff --git a/tslang/include/TypeScript/Defines.h b/tslang/include/TypeScript/Defines.h index 137578aa7..f31f78981 100644 --- a/tslang/include/TypeScript/Defines.h +++ b/tslang/include/TypeScript/Defines.h @@ -89,6 +89,15 @@ #define SIZE_NAME ".size" #define INSTANCEOF_NAME ".instanceOf" #define INSTANCEOF_PARAM_NAME "rttiParam" +// Every interface vtable starts with the `.instanceOf` of what implements it, ahead of the +// interface's own members: a class's own `.instanceOf`, or, for an object literal, which is no +// instance of any class, INSTANCEOF_NONE_NAME, which answers false. That lets `i instanceof C` +// and `anyHoldingAnInterface` ask an interface value what it is. An interface member's slot +// (InterfaceSymbolRefOp's index) is its index among the members, so the vtable slot is that plus +// INTERFACE_VTABLE_HEADER_SLOTS. +#define INTERFACE_VTABLE_INSTANCEOF_SLOT 0 +#define INTERFACE_VTABLE_HEADER_SLOTS 1 +#define INSTANCEOF_NONE_NAME ".instanceOf.none" #define MAIN_ENTRY_NAME "main" #define TS_NEST_ATTRIBUTE "ts.nest" #define THIS_TEMPVAR_NAME ".this" diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h index 514ec6d3d..8cc25a074 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRGenStore.h @@ -2,6 +2,7 @@ #define MLIR_TYPESCRIPT_MLIRGENSTORE_H_ #include "TypeScript/DOM.h" +#include "TypeScript/Defines.h" #include "TypeScript/MLIRLogic/MLIRHelper.h" #include "llvm/ADT/StringMap.h" @@ -895,8 +896,21 @@ struct ClassInfo base->getVirtualTable(vtable, methodSlots, interfaceSlots); } - // TODO: we need to process .Rtti first - // TODO: then we need to process .instanceOf next + // `.instanceOf` is slot 0 of every class vtable: an object reached with no static type (an + // `any` being unboxed, `x instanceof C` on an opaque value) is asked through that slot + // (mlirGenInstanceOfOpaque). A root class puts it there ahead of its interfaces, and a + // derived class overrides it in place. Before this, a root class that implemented an + // interface kept the interface's vtable in slot 0, and `anyValue` called it. + if (baseClasses.empty()) + { + auto instanceOf = std::find_if(methods.begin(), methods.end(), + [](auto &method) { return method.name == INSTANCEOF_NAME; }); + if (instanceOf != methods.end() && instanceOf->isVirtual && !methodSlots.contains(instanceOf->name)) + { + methodSlots[instanceOf->name] = vtable.size(); + vtable.push_back({*instanceOf, false}); + } + } // do vtable for current class for (auto &implement : implements) diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index 212caa511..b9a79d179 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -5605,8 +5605,9 @@ struct InterfaceSymbolRefOpLowering : public TsLlvmPattern(loc, th.getPtrType(), transformed.getInterfaceVal(), MLIRHelper::getStructIndex(rewriter, THIS_VALUE_INDEX)); - auto methodOrFieldPtr = - rewriter.create(loc, th.getPtrType(), vtable, interfaceSymbolRefOp.getIndex()); + // the vtable starts with the implementer's `.instanceOf` - see INTERFACE_VTABLE_HEADER_SLOTS + auto methodOrFieldPtr = rewriter.create( + loc, th.getPtrType(), vtable, interfaceSymbolRefOp.getIndex() + INTERFACE_VTABLE_HEADER_SLOTS); if (auto boundFunc = dyn_cast(interfaceSymbolRefOp.getType())) { diff --git a/tslang/lib/TypeScript/MLIRGenCast.cpp b/tslang/lib/TypeScript/MLIRGenCast.cpp index 7c7c7c8e4..3810287bd 100644 --- a/tslang/lib/TypeScript/MLIRGenCast.cpp +++ b/tslang/lib/TypeScript/MLIRGenCast.cpp @@ -1,6 +1,7 @@ // Cast-family methods of MLIRGenImpl (see MLIRGenImpl.h). #include "MLIRGenImpl.h" +#include "TypeScript/MLIRLogic/MLIRRTTIHelperVC.h" namespace typescript { @@ -270,7 +271,66 @@ namespace mlirgen return NewClassInstanceWithSettingFields(location, classType, fields, values, genContext); } - ValueOrLogicalResult MLIRGenImpl::castFieldsToClass(mlir::Location location, mlir::Value value, + // `i`, `i as C`, or narrowing by `i instanceof C`: the interface holds an object, and if that + // object is a C, the result is that very object - asked through slot 0 of the interface's + // vtable (INTERFACE_VTABLE_HEADER_SLOTS). If it is not (another class, or an object literal), + // there is no C to hand back, and the cast throws, as `anyValue` does. It used to build a + // new C out of the interface's fields, which crashed the compiler for any class with a field + // the interface does not name - `class B implements I` with a field `x` was enough. + ValueOrLogicalResult MLIRGenImpl::castInterfaceToClass(mlir::Location location, mlir::Value value, + mlir_ts::ClassType classType, const GenContext &genContext) + { + // Where every field of the class is a field of the interface, the old conversion - a new + // C filled from the interface's fields - still answers for what is not a C, as before. + auto fields = mlir::cast(classType.getStorageType()).getFields(); + auto interfaceInfo = getInterfaceInfoByFullName(mlir::cast(value.getType()).getName().getValue()); + auto fieldsInInterface = interfaceInfo && llvm::all_of(fields, [&](auto &field) { + auto name = dyn_cast_or_null(field.id); + return (name && name.getValue().starts_with(".")) || interfaceInfo->findField(field.id); + }); + + auto classRefVal = builder.create( + location, classType, mlir::FlatSymbolRefAttr::get(builder.getContext(), classType.getName().getValue())); + auto isInstance = mlirGenInstanceOfInterface(location, value, classRefVal, genContext); + if (isInstance.failed_or_no_value()) + { + return mlir::failure(); + } + + MLIRCodeLogicHelper mclh(builder, location, compileOptions); + return mclh.conditionalValue( + V(isInstance), + [&]() { + auto thisPtr = builder.create(location, getOpaqueType(), value); + return ValueOrLogicalResult(builder.create(location, classType, thisPtr)); + }, + [&](mlir::Type) { + if (fieldsInInterface) + { + return castFieldsToClass(location, value, fields, classType, genContext); + } + + auto message = builder.create(location, getStringType(), + getStringAttr("Can't cast from interface")); + builder.create(location, message); + + MLIRRTTIHelperVC rtti(builder, theModule, compileOptions); + setCatchCopyThunkBuilder(rtti); + if (!rtti.setRTTIForType(location, message.getType(), [&](StringRef classFullName) { + return getClassInfoByFullName(classFullName); + })) + { + emitError(location, "Not supported type in throw"); + return ValueOrLogicalResult(mlir::failure()); + } + + // not reached; the branch still needs a value of the class type + auto nullValue = builder.create(location, getNullType()); + return ValueOrLogicalResult(builder.create(location, classType, nullValue)); + }); + } + + ValueOrLogicalResult MLIRGenImpl::castFieldsToClass(mlir::Location location, mlir::Value value, ::llvm::ArrayRef<::mlir::typescript::FieldInfo> fields, mlir_ts::ClassType classType, const GenContext &genContext, bool errorAsWarning) { @@ -1177,8 +1237,7 @@ namespace mlirgen } else if (auto classType = dyn_cast(type)) { - fields = mlir::cast(classType.getStorageType()).getFields(); - return castFieldsToClass(location, value, fields, classType, genContext); + return castInterfaceToClass(location, value, classType, genContext); } } @@ -1691,7 +1750,7 @@ namespace mlirgen StringMap typeOfs; SmallVector classInstances; - ss << S("function __unbox(a: any) : T {\n"); + ss << S("function __unbox(a: any) : T {\nconst b = a;\n"); auto subType = type; auto hasUnsupportedType = false; mlir::TypeSwitch(subType) @@ -1749,6 +1808,24 @@ namespace mlirgen } ss << S(" }\n"); + + // An `any` holding an interface value holds an object behind it, which may be one + // of these classes: the interface's vtable answers (INTERFACE_VTABLE_HEADER_SLOTS). + // `b` is `a` before `typeof` narrowed it to the first word of the box. + if (!typeOfs.contains("interface")) + { + ss << S(" else if (typeof a == 'interface') { const i = b;\n"); + for (auto [index, _] : enumerate(classInstances)) + { + ss << S("if (i instanceof TYPE_INST_ALIAS"); + ss << index; + ss << S(") return i;\n"); + } + + ss << S(" }\n"); + } } else { @@ -1830,17 +1907,24 @@ namespace mlirgen funcCallGenContext.typeAliasMap.insert({"TYPE_INST_ALIAS" + std::to_string(index), instanceOfType}); } + // Any interface type will do to read an interface value back out of the box: they all + // lower to one layout, and nothing but the vtable and `this` is read from it. A private + // name of its own keeps it from being looked up as a declared interface. + funcCallGenContext.typeAliasMap.insert( + {"TYPE_ANY_INTERFACE_ALIAS", + mlir_ts::InterfaceType::get(builder.getContext(), mlir::FlatSymbolRefAttr::get(builder.getContext(), ".any_interface"))}); + SmallVector operands; operands.push_back(value); NodeFactory nf(NodeFactoryFlags::None); return mlirGenCallExpression( - location, - funcResult, - { - nf.createTypeReferenceNode(nf.createIdentifier(S(".TYPE_ALIAS_T")).as()), - }, - operands, + location, + funcResult, + { + nf.createTypeReferenceNode(nf.createIdentifier(S(".TYPE_ALIAS_T")).as()), + }, + operands, funcCallGenContext); } diff --git a/tslang/lib/TypeScript/MLIRGenClasses.cpp b/tslang/lib/TypeScript/MLIRGenClasses.cpp index ad5ca40cc..d3dde59bc 100644 --- a/tslang/lib/TypeScript/MLIRGenClasses.cpp +++ b/tslang/lib/TypeScript/MLIRGenClasses.cpp @@ -1675,6 +1675,19 @@ genContext); return result; } + // slot 0: the class's own `.instanceOf` (INTERFACE_VTABLE_HEADER_SLOTS), ahead of the + // interface's members, so an interface value can be asked what class it holds. A class + // with no `.instanceOf` (no vtable of its own) is an instance of nothing it can answer for. + if (auto *instanceOf = newClassPtr->findMethod(INSTANCEOF_NAME)) + { + virtualTable.insert(virtualTable.begin(), VirtualMethodOrFieldInfo(*instanceOf)); + } + else + { + auto noInstanceOf = mlirGenNoInstanceOfFunction(location); + virtualTable.insert(virtualTable.begin(), VirtualMethodOrFieldInfo(noInstanceOf)); + } + // a method implementing this interface can itself be owned by a dynamically imported // class (not just a base of it) - such a slot can't be a link-time constant SymbolRefOp // and must be resolved at runtime instead (see the dynamic-import handling in the method diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 49362abd8..7cc245db3 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -3031,6 +3031,7 @@ class MLIRGenImpl int firstParam = 0, bool isPublic = false); mlir::LogicalResult mlirGenCatchCopyThunk(mlir::Location location, StringRef name, mlir::Type source, mlir::Type target); + MethodInfo mlirGenNoInstanceOfFunction(mlir::Location location); void setCatchCopyThunkBuilder(MLIRRTTIHelperVC &rtti); @@ -4893,6 +4894,42 @@ class MLIRGenImpl // generates nothing - `if (a instanceof C) return a;` in the generated `___unbox` lost its // test, so `anyValue` compiled into a cast that always throws, with no error shown. ValueOrLogicalResult mlirGenInstanceOfOpaque(mlir::Location location, mlir::Value thisPtrValue, mlir::Value classRefVal, const GenContext &genContext) + { + // an instance's first word is its class's vtable + auto vtablePtr = builder.create(location, getOpaqueType(), + thisPtrValue, 0 /*VTABLE index*/); + return mlirGenInstanceOfThroughVTable(location, vtablePtr, thisPtrValue, classRefVal, genContext); + } + + // `i instanceof C` for an interface value: its own vtable answers, since what it holds may be + // an object literal, which has no class vtable to ask (INTERFACE_VTABLE_HEADER_SLOTS). A null + // interface is an instance of nothing. + ValueOrLogicalResult mlirGenInstanceOfInterface(mlir::Location location, mlir::Value interfaceValue, mlir::Value classRefVal, const GenContext &genContext) + { + auto isSet = cast(location, getBooleanType(), interfaceValue, genContext); + if (isSet.failed_or_no_value()) + { + return mlir::failure(); + } + + MLIRCodeLogicHelper mclh(builder, location, compileOptions); + return mclh.conditionalValue( + V(isSet), + [&]() { + auto vtable = builder.create(location, getOpaqueType(), interfaceValue); + auto thisPtr = builder.create(location, getOpaqueType(), interfaceValue); + return mlirGenInstanceOfThroughVTable(location, vtable, thisPtr, classRefVal, genContext); + }, + [&](mlir::Type) { + return ValueOrLogicalResult( + builder.create(location, getBooleanType(), builder.getBoolAttr(false))); + }); + } + + // Asks `thisPtrValue` whether it is a `classRefVal` through slot 0 of `vtablePtr`: the class's + // `.instanceOf` for a class vtable, and the implementer's for an interface vtable. + ValueOrLogicalResult mlirGenInstanceOfThroughVTable(mlir::Location location, mlir::Value vtablePtr, mlir::Value thisPtrValue, + mlir::Value classRefVal, const GenContext &genContext) { auto classType = dyn_cast(classRefVal.getType()); if (!classType) @@ -4901,13 +4938,9 @@ class MLIRGenImpl return mlir::failure(); } - // get VTable we can use VTableOffset - auto vtablePtr = builder.create(location, getOpaqueType(), - thisPtrValue, 0 /*VTABLE index*/); - - // get InstanceOf method, this is 0 index in vtable + // `.instanceOf` is slot 0 of a class vtable and of an interface vtable alike auto instanceOfPtr = builder.create( - location, getOpaqueType(), vtablePtr, 0 /*InstanceOf index*/); + location, getOpaqueType(), vtablePtr, INTERFACE_VTABLE_INSTANCEOF_SLOT); auto classInfo = getClassInfoByFullName(classType.getName().getValue()); @@ -5038,6 +5071,11 @@ class MLIRGenImpl { return mlirGenInstanceOfOpaque(location, resultLeftValue, resultRightValue, genContext); } + + if (isa(resultLeftfType)) + { + return mlirGenInstanceOfInterface(location, resultLeftValue, resultRightValue, genContext); + } } #endif @@ -11223,7 +11261,9 @@ class MLIRGenImpl ValueOrLogicalResult castTupleToClass(mlir::Location location, mlir::Value value, mlir_ts::TupleType srcTupleType, ArrayRef fields, mlir_ts::ClassType classType, const GenContext &genContext, bool errorAsWarning = false); - ValueOrLogicalResult castFieldsToClass(mlir::Location location, mlir::Value value, + ValueOrLogicalResult castInterfaceToClass(mlir::Location location, mlir::Value value, mlir_ts::ClassType classType, + const GenContext &genContext); + ValueOrLogicalResult castFieldsToClass(mlir::Location location, mlir::Value value, ::llvm::ArrayRef<::mlir::typescript::FieldInfo> fields, mlir_ts::ClassType classType, const GenContext &genContext, bool errorAsWarning = false); diff --git a/tslang/lib/TypeScript/MLIRGenInterfaces.cpp b/tslang/lib/TypeScript/MLIRGenInterfaces.cpp index be1eb78e5..34de958b9 100644 --- a/tslang/lib/TypeScript/MLIRGenInterfaces.cpp +++ b/tslang/lib/TypeScript/MLIRGenInterfaces.cpp @@ -288,8 +288,8 @@ namespace mlirgen LLVM_DEBUG(llvm::dbgs() << "\n!!\n\t vtable method: " << method.name << "\n\t object method ref: " << V(methodRef) << "\n\n";); - // where to save - auto combinedVirtualIndex = method.virtualIndex + vtableOffset; + // where to save; the vtable starts with `.instanceOf` (INTERFACE_VTABLE_HEADER_SLOTS) + auto combinedVirtualIndex = method.virtualIndex + vtableOffset + INTERFACE_VTABLE_HEADER_SLOTS; auto fieldInfoVT = mth.getFieldInfoByIndex(vtableType, combinedVirtualIndex); auto methodRefVT = builder.create(location, fieldInfoVT.type, varVTable, combinedVirtualIndex); @@ -309,6 +309,47 @@ namespace mlirgen return mlir::failure(); } + // The `.instanceOf` of an object literal cast to an interface: it is an instance of no class. + // One per module, private, with the same signature as a class's `.instanceOf` reached through + // a vtable (`this`, the asked class's rtti) - see INTERFACE_VTABLE_HEADER_SLOTS. + MethodInfo MLIRGenImpl::mlirGenNoInstanceOfFunction(mlir::Location location) + { + MethodInfo methodInfo; + methodInfo.name = INSTANCEOF_NAME; + methodInfo.funcName = INSTANCEOF_NONE_NAME; + methodInfo.funcType = getFunctionType({getOpaqueType(), getStringType()}, {getBooleanType()}, false); + methodInfo.isStatic = false; + methodInfo.isVirtual = true; + methodInfo.isAbstract = false; + methodInfo.virtualIndex = INTERFACE_VTABLE_INSTANCEOF_SLOT; + methodInfo.orderWeight = 0; + methodInfo.accessLevel = mlir_ts::AccessLevel::Public; + + if (theModule.lookupSymbol(INSTANCEOF_NONE_NAME)) + { + return methodInfo; + } + + mlir::OpBuilder::InsertionGuard guard(builder); + + // compiler glue with no source to step through: no location, so no debug scope to get + // wrong (the caller's location is inside whatever function the cast is in) + auto noLocation = mlir::UnknownLoc::get(builder.getContext()); + auto funcOp = mlir_ts::FuncOp::create(noLocation, INSTANCEOF_NONE_NAME, methodInfo.funcType); + funcOp.setPrivate(); + funcOp->setAttr("internal_linkage", builder.getUnitAttr()); + + auto &entryBlock = *funcOp.addEntryBlock(); + builder.setInsertionPointToStart(&entryBlock); + auto entryOp = builder.create(noLocation, mlir_ts::RefType::get(getBooleanType())); + auto falseValue = builder.create(noLocation, getBooleanType(), builder.getBoolAttr(false)); + builder.create(noLocation, falseValue, entryOp.getReference()); + builder.create(noLocation, entryOp.getReference()); + + theModule.push_back(funcOp); + return methodInfo; + } + mlir::LogicalResult MLIRGenImpl::mlirGenObjectVirtualTableDefinitionForInterface(mlir::Location location, mlir_ts::ObjectType objectType, InterfaceInfo::TypePtr newInterfacePtr, @@ -334,6 +375,15 @@ namespace mlirgen return result; } + // slot 0: an object literal is an instance of no class (INTERFACE_VTABLE_HEADER_SLOTS) + auto noInstanceOf = mlirGenNoInstanceOfFunction(location); + if (!noInstanceOf.funcType) + { + return mlir::failure(); + } + + virtualTable.insert(virtualTable.begin(), VirtualMethodOrFieldInfo(noInstanceOf)); + // register global auto fullClassInterfaceVTableFieldName = interfaceVTableNameForObject(objectType, newInterfacePtr); registerVariable( @@ -475,15 +525,21 @@ namespace mlirgen location, virtTuple, castedPtr, vtableValue, MLIRHelper::getStructIndex(builder, fieldIndex)); } + else if (!methodOrField.methodInfo.funcName.empty()) + { + // the `.instanceOf` slot this function puts first. Every other entry is + // a field: getInterfaceVirtualTableForObject hardcodes methodsAsFields. + auto methodValueRef = builder.create( + location, methodOrField.methodInfo.funcType, + mlir::FlatSymbolRefAttr::get(builder.getContext(), methodOrField.methodInfo.funcName)); + vtableValue = builder.create( + location, virtTuple, methodValueRef, vtableValue, + MLIRHelper::getStructIndex(builder, fieldIndex)); + } else { - // unreachable: getInterfaceVirtualTableForObject (this function's - // only caller of newInterfacePtr->getVirtualTable) hardcodes - // methodsAsFields=true, so every entry in `virtualTable` is - // guaranteed isField=true - this branch (a real method reaching - // here as something other than a field) can never be taken for - // an object's interface vtable. Fail gracefully rather than crash - // in case that invariant is ever violated by a future caller. + // unreachable: see above. Fail gracefully rather than crash in case that + // invariant is ever violated by a future caller. emitError(location, "interface method could not be resolved for this object"); return TypeValueInitType{mlir::Type(), mlir::Value(), TypeProvided::Yes}; } diff --git a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp index db9c161cd..f408a39f3 100644 --- a/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp +++ b/tslang/lib/TypeScript/OwnedReturnConsumptionPass.cpp @@ -731,7 +731,7 @@ class OwnedReturnConsumptionPass // The vtables say it exactly. An interface value is built by `ts.NewInterface` over one of // them, and every vtable in the module is a global: a class implementing `Thing` gets // `Sphere.Thing..vtbl`, an object literal gets `Thing...vtbl`, and the interface's own - // name is a component of both. So the candidates for slot `index` of interface `I` are that + // name is a component of both. So the candidates for member `index` of interface `I` are its // slot in every vtable global naming `I`, and a slot that is absent - not initialised from a // symbol - makes the whole call unclassifiable rather than being skipped. static bool interfaceCallReturnsOwned(mlir_ts::CallIndirectOp callOp, @@ -762,7 +762,9 @@ class OwnedReturnConsumptionPass } auto interfaceName = interfaceType.getName().getValue(); - auto index = (int64_t)interfaceRefOp.getIndex(); + // The op's index is the member's, and the vtable global has the `.instanceOf` header in + // front of the members: read at the index itself, the call looked at its neighbour. + auto index = (int64_t)interfaceRefOp.getIndex() + INTERFACE_VTABLE_HEADER_SLOTS; auto sawCandidate = false; for (auto &vtable : vtableSlots) diff --git a/tslang/lib/TypeScript/OwnershipFacts.h b/tslang/lib/TypeScript/OwnershipFacts.h index 00ee0cde1..6465c42e8 100644 --- a/tslang/lib/TypeScript/OwnershipFacts.h +++ b/tslang/lib/TypeScript/OwnershipFacts.h @@ -380,12 +380,21 @@ inline int borrowedParam(mlir::Value value, int depth = 0) } auto *def = value.getDefiningOp(); + // out of an opaque pointer, out of an interface (its `this`), or reading an interface value + // back out of an `any` box: the same block if (auto castOp = mlir::dyn_cast_or_null(def); - castOp && mlir::isa(castOp.getIn().getType())) + castOp && (mlir::isa(castOp.getIn().getType()) || + (mlir::isa(castOp.getIn().getType()) && + mlir::isa(castOp.getType())))) { return borrowedParam(castOp.getIn(), depth + 1); } + if (auto extractOp = mlir::dyn_cast_or_null(def)) + { + return borrowedParam(extractOp.getOperand(), depth + 1); + } + if (auto unboxOp = mlir::dyn_cast_or_null(def)) { return borrowedParam(unboxOp.getIn(), depth + 1); diff --git a/tslang/lib/TypeScript/OwnershipSignaturePass.cpp b/tslang/lib/TypeScript/OwnershipSignaturePass.cpp index 24bc9ced6..fa5c06f55 100644 --- a/tslang/lib/TypeScript/OwnershipSignaturePass.cpp +++ b/tslang/lib/TypeScript/OwnershipSignaturePass.cpp @@ -335,18 +335,29 @@ class OwnershipSignaturePass : public mlir::PassWrapper(def); auto slot = castOp ? castOp.getIn().getDefiningOp() : mlir_ts::VTableOffsetRefOp(); - auto vtable = slot ? slot.getVtable().getDefiningOp() : mlir_ts::VTableOffsetRefOp(); - return vtable && slot.getIndex() == 0 && vtable.getIndex() == 0; + if (!slot || slot.getIndex() != INTERFACE_VTABLE_INSTANCEOF_SLOT) + { + return false; + } + + // a class vtable, read out of an object's first word, or an interface's own vtable, + // whose slot 0 is its implementer's `.instanceOf` (INTERFACE_VTABLE_HEADER_SLOTS) + auto *vtable = slot.getVtable().getDefiningOp(); + if (auto classVTable = mlir::dyn_cast_or_null(vtable)) + { + return classVTable.getIndex() == 0; + } + + return mlir::isa_and_nonnull(vtable); } // Is every use of this function value the callee of a call - through `ts.GetMethod`, with diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 1fb3a54b1..cfd7974a5 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -524,6 +524,8 @@ tslang_add_test(NAME test-compile-00-interface-new COMMAND test-runner "${PROJEC tslang_add_test(NAME test-compile-00-interface-indexer COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts") tslang_add_test(NAME test-compile-00-class-or-interface-to-tuple COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_or_interface_to_tuple.ts") tslang_add_test(NAME test-compile-00-any COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00any.ts") +tslang_add_test(NAME test-compile-00-any-unbox-interface-class COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_unbox_interface_class.ts") +tslang_add_test(NAME test-compile-00-interface-instanceof COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_instanceof.ts") tslang_add_test(NAME test-compile-01-any COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/01any.ts") tslang_add_test(NAME test-compile-00-any-types COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_types.ts") tslang_add_test(NAME test-compile-00-any-compare COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_compare.ts") @@ -979,6 +981,8 @@ tslang_add_test(NAME test-jit-00-interface-new COMMAND test-runner -jit "${PROJE tslang_add_test(NAME test-jit-00-interface-indexer COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_indexer.ts") tslang_add_test(NAME test-jit-00-class-or-interface-to-tuple COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00class_or_interface_to_tuple.ts") tslang_add_test(NAME test-jit-00-any COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00any.ts") +tslang_add_test(NAME test-jit-00-any-unbox-interface-class COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_unbox_interface_class.ts") +tslang_add_test(NAME test-jit-00-interface-instanceof COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00interface_instanceof.ts") tslang_add_test(NAME test-jit-01-any COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/01any.ts") tslang_add_test(NAME test-jit-00-any-types COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_types.ts") tslang_add_test(NAME test-jit-00-any-compare COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00any_compare.ts") @@ -2398,6 +2402,18 @@ set_tests_properties(test-own-inference-erases-birth-takes PROPERTIES PASS_REGULAR_EXPRESSION "ts.Release" FAIL_REGULAR_EXPRESSION "ts\\.Retain|error|Stack dump") +# Under rc, a call through an interface carries the result its implementation retained: no +# `ts.CallIndirect` returning a C through an interface slot is left without `__owned_result` +# (`__owned_result_named` alone only says it initialises a `const`). +tslang_add_test(NAME test-jit-rc-interface-call-owned-result COMMAND test-runner -jit -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/own/rc_interface_call_owned_result.ts") +tslang_add_test(NAME test-compile-rc-interface-call-owned-result COMMAND test-runner -mm=rc "${PROJECT_SOURCE_DIR}/test/tester/own/rc_interface_call_owned_result.ts") +add_test(NAME test-rc-interface-call-owned-result-marked + COMMAND $ --emit=mlir --no-default-lib -mm=rc + "${PROJECT_SOURCE_DIR}/test/tester/own/rc_interface_call_owned_result.ts") +set_tests_properties(test-rc-interface-call-owned-result-marked + PROPERTIES PASS_REGULAR_EXPRESSION "__owned_result" + FAIL_REGULAR_EXPRESSION "CallIndirect\"\\([^)]*\\) ({__owned_result_named} )?: \\(!ts\\.funca` called the interface's vtable as a function and crashed, under +// every memory model. + +interface I { + get(): number; +} + +interface J { + twice(): number; +} + +class Plain { + x: number = 1; +} + +class B implements I { + x: number = 5; + get() { + return this.x; + } +} + +class Both implements I, J { + x: number = 7; + get() { + return this.x; + } + + twice() { + return this.x * 2; + } +} + +class D extends B implements J { + y: number = 3; + twice() { + return this.y * 2; + } +} + +function main() { + const p: any = new Plain(); + const b: any = new B(); + const both: any = new Both(); + const d: any = new D(); + + assert((p).x == 1); + assert((b).get() == 5); + assert((both).twice() == 14); + assert((d).twice() == 6); + assert((d).get() == 5); + + assert(b instanceof B); + assert(!(b instanceof D)); + assert(d instanceof B); + assert(d instanceof D); + + print("done."); +} diff --git a/tslang/test/tester/tests/00interface_instanceof.ts b/tslang/test/tester/tests/00interface_instanceof.ts new file mode 100644 index 000000000..a7e2812bd --- /dev/null +++ b/tslang/test/tester/tests/00interface_instanceof.ts @@ -0,0 +1,83 @@ +// An interface value can be asked what it holds: slot 0 of every interface vtable is the +// implementer's `.instanceOf`, or, for an object literal, one that answers false. +// +// Before, `i instanceof B` on an interface was decided at compile time and was always false; +// `i`, `i as B` and narrowing by `instanceof` crashed the compiler (it tried to build a new B +// out of the interface's fields); and `a`, with `a` an `any` holding an interface, threw. + +interface I { + get(): number; +} + +interface J { + twice(): number; +} + +class B implements I, J { + x: number = 5; + get() { + return this.x; + } + + twice() { + return this.x * 2; + } +} + +class D extends B { + y: number = 1; +} + +class Other implements I { + get(): number { + return 9; + } +} + +function main() { + const i: I = new B(); + const j: J = new B(); + const d: I = new D(); + const lit: I = { get(): number { return 3; } }; + + assert(i instanceof B); + assert(!(i instanceof Other)); + assert(!(i instanceof D)); + assert(j instanceof B); + assert(d instanceof B); + assert(d instanceof D); + assert(!(lit instanceof B)); + + // methods and fields still reach their slots, after the one in front of them + assert(i.get() == 5); + assert(j.twice() == 10); + assert(lit.get() == 3); + + // a downcast is the very object + const b = i; + b.x = 6; + assert(i.get() == 6); + assert((i as B).twice() == 12); + if (d instanceof D) { + assert(d.y == 1); + } + + // a class with no field outside the interface is still converted from what is not one + assert((i as Other).get() == 9); + + // an `any` holding an interface + const a: any = i; + assert((a).x == 6); + const al: any = lit; + let caught = false; + try { + const nb = al; + print(nb.x); + } catch (e) { + caught = true; + } + + assert(caught); + + print("done."); +}