Skip to content

Commit 1bfbb2e

Browse files
A null class object, null or undefined given to an interface is a null interface whose methods throw (#665)
* A class object or null given to an interface or null becomes an interface `let opt: I | null = flag ? mk(5) : null` (`C | null` to `I | null`) was a plain ts.Cast: the class pointer was read as an interface, which the lowering could not make ("invalid cast from '!llvm.ptr' to '!llvm.struct<(ptr, ptr, ptr)>'"), under every model. The program crashed, or called through a wrong vtable into code that allocated without end. castToNullableUnionType now converts the class member the way `let a: I = mk()` does (the vtable lookup is shared, mlirGenClassInterfaceVTable), and null stays null. Only the vtable is chosen by a test for null - it may be read through the object - and a null object is seen through a null vtable, which is what an interface compares to null by. The ts.NewInterface is built over the object outside the test, so it stays a view of the union for the ownership passes. Two of them now look through the class read out of the union: a global's initializer (`let g: I | null = maybe()` was born pointing at freed memory under rc and own) and a const's view (`const o: I | null = c` was folded with no count, and `c = null` freed what it read). Tests read a field before any method call or allocation, so a regression fails an assert instead of running away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A null class object given to an interface is a null interface `C | null` is stored as `C`, so `C | null` to `I | null` is the class-to-interface cast of `C` to `I`: the union path now only reads the class out of the union and makes that cast. The null handling moves into the class-to-interface cast itself, where it was missing for a plain `C` too (non-strict null): a null class object was paired with its class's vtable, so the interface was not `=== null`, and for a class naming the interface, whose vtable is read through the object, the cast read through the null pointer and crashed. Only the vtable is chosen by the test for null; the interface is still built over the object outside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * -mm=own: an interface over a class object is made with its class's vtable A class object's vtable for an interface is now chosen by a test for null (castToInterfaceSpecialCases), so ts.NewInterface's vtable is a ts.If's result or, by the time the signature pass runs, a block argument its branches pass it to. The pass took that for a vtable it could not trace and opened the interface ("reached through an interface, whose calls are not given its facts"): 17 own tests failed. It now follows each branch's vtable, skipping a null object's, and counts a class vtable passed along a branch as one that only makes values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A null interface is seen through a vtable whose methods throw A null interface - `I | null` holding null, a null class object seen through `I`, `null` or `undefined` given to `I` - is an interface over a null `this`, seen through the interface's null vtable (mlirGenNullInterfaceVTable, `<I>.null..vtbl`, one per interface and module). Each of its methods is a stub that throws, as a call of an abstract method would: `new Error("method is abstract")`, or the message itself without the default library, which has no `Error`. A field's slot is offset 0, so reading one reads through the null `this`, as a null class object's field does; an optional member is absent (-1); `.instanceOf` answers false. A null class object was seen through a null vtable, a call of one of its methods jumped to address 0, and `null` cast to an interface had no vtable at all. An interface is now tested for null, and compared, by its `this` (UnaryBinLogicalOrHelper, and CastLogicHelper's interface-to-boolean), not its vtable: a null interface has one, and two interfaces over the same object are the same value (they were compared by vtable, so two objects of one class were equal). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The ownership passes see a null interface as null, not as its vtable The null vtable and its throwing stubs are compiler glue the ownership passes must not read as a way an interface is implemented: - A null interface stays a cast of null in the IR the passes see - `null`/`undefined` given to an interface, and a null class object's vtable choice - and the cast names the null vtable (NULL_INTERFACE_VTABLE_ATTR_NAME), which the lowering puts in (CastLogicHelper::nullInterfaceVTable). An `AddressOf` of it in a function opened the interface and lost the `reduce` callback's facts in the own ray tracer. - The signature pass leaves the null vtable out of the interface vtables, and a stub's symbol there does not open it; the rc pass leaves both out of what a call through an interface or a value may reach - a stub returns nothing, so it does not switch off the whole-module "every heap result is retained" answer either. - A stub is a function of its own: its `throw` no longer walks out into the scopes of the function whose cast made it, and mlirGenFunctionBody no longer gives a function returning nothing the return variable of the function being generated around it. - A discovery run makes no null vtable; one it left without a body is made again. And `throw` no longer turns off -mm=own's "a C function of numbers destroys nothing" (spec 31): the exception tables name the C++ runtime's type_info vtable, a foreign global, which counted as a way to hand C a function. It is marked (EH_TYPE_INFO_ATTR_NAME) and left out. Any module with a `throw` hit it (`sqrt` then "may release"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 144528a commit 1bfbb2e

18 files changed

Lines changed: 905 additions & 65 deletions

‎tslang/include/TypeScript/Defines.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@
173173
// INTERFACE_VTABLE_HEADER_SLOTS.
174174
#define INTERFACE_VTABLE_INSTANCEOF_SLOT 0
175175
#define INTERFACE_VTABLE_HEADER_SLOTS 1
176+
// on the global of an interface's null vtable (mlirGenNullInterfaceVTable): a null interface is seen
177+
// through it, and nothing implements the interface by it
178+
#define NULL_INTERFACE_VTABLE_ATTR_NAME "__null_interface_vtable"
179+
// on the C++ runtime's type_info vtable a `throw`'s exception tables name (`??_7type_info@@6B@`,
180+
// `_ZTVN10__cxxabiv1...`): a foreign global, but no program value is ever read out of it or stored into it
181+
#define EH_TYPE_INFO_ATTR_NAME "__eh_type_info"
176182
// on a ts.Cast from one interface to another whose members are not where the source has them (an
177183
// interface that does not extend the source): for each slot of the target's vtable, the slot of the
178184
// source's that holds it, or -1 for an optional member the source lacks. The cast then gives the

‎tslang/include/TypeScript/LowerToLLVM/CastLogicHelper.h‎

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,32 @@ class CastLogicHelper
5959
external = true;
6060
}
6161

62+
// The address of the vtable a null interface is seen through: the one MLIRGen named on the cast
63+
// (mlirGenNullInterfaceVTableCast), whose methods throw; none when it named none, as for a cast
64+
// the lowering makes itself.
65+
mlir::Value nullInterfaceVTableAddress()
66+
{
67+
auto vtableRef = op ? op->getAttrOfType<mlir::FlatSymbolRefAttr>(NULL_INTERFACE_VTABLE_ATTR_NAME) : mlir::FlatSymbolRefAttr();
68+
if (!vtableRef)
69+
{
70+
return mlir::Value();
71+
}
72+
73+
return rewriter.create<LLVM::AddressOfOp>(loc, th.getPtrType(), vtableRef.getValue());
74+
}
75+
76+
// the same, as an operand of an interface made here, or `noVTable`
77+
mlir::Value nullInterfaceVTable(mlir::Value noVTable)
78+
{
79+
auto vtableAddress = nullInterfaceVTableAddress();
80+
if (!vtableAddress)
81+
{
82+
return noVTable;
83+
}
84+
85+
return rewriter.create<mlir_ts::DialectCastOp>(loc, mlir_ts::OpaqueType::get(rewriter.getContext()), vtableAddress);
86+
}
87+
6288
mlir::Value cast(mlir::Value in, mlir::Type inType, mlir::Type resType)
6389
{
6490
auto inLLVMType = tch.convertType(inType);
@@ -332,7 +358,16 @@ class CastLogicHelper
332358
if (auto ifaceType = dyn_cast<mlir_ts::InterfaceType>(resType))
333359
{
334360
// create null interface
335-
return rewriter.create<mlir_ts::NewInterfaceOp>(loc, ifaceType, in, in);
361+
return rewriter.create<mlir_ts::NewInterfaceOp>(loc, ifaceType, in, nullInterfaceVTable(in));
362+
}
363+
364+
// the vtable a null class object is seen through as an interface (castToInterfaceSpecialCases)
365+
if (isa<mlir_ts::OpaqueType>(resType))
366+
{
367+
if (auto vtableAddress = nullInterfaceVTableAddress())
368+
{
369+
return vtableAddress;
370+
}
336371
}
337372

338373
if (auto resHybridFunc = dyn_cast<mlir_ts::HybridFunctionType>(resType))
@@ -424,10 +459,11 @@ class CastLogicHelper
424459
return rewriter.create<mlir_ts::ConstantOp>(loc, llvmBoolType, rewriter.getBoolAttr(true));
425460
}
426461

462+
// set when its `this` is: a null interface may be seen through a vtable (UnaryBinLogicalOrHelper)
427463
if (auto ifaceType = dyn_cast<mlir_ts::InterfaceType>(inType))
428464
{
429465
auto ptrValue =
430-
rewriter.create<mlir_ts::ExtractInterfaceVTableOp>(loc, mlir_ts::OpaqueType::get(rewriter.getContext()), in);
466+
rewriter.create<mlir_ts::ExtractInterfaceThisOp>(loc, mlir_ts::OpaqueType::get(rewriter.getContext()), in);
431467
auto inLLVMType = tch.convertType(ptrValue.getType());
432468
auto llvmBoolType = tch.convertType(boolType);
433469
return castLLVMTypes(ptrValue, inLLVMType, boolType, llvmBoolType);
@@ -556,7 +592,7 @@ class CastLogicHelper
556592
{
557593
// create null interface
558594
auto nullVal = rewriter.create<mlir_ts::NullOp>(loc, mlir_ts::NullType::get(ifaceType.getContext()));
559-
return rewriter.create<mlir_ts::NewInterfaceOp>(loc, ifaceType, nullVal, nullVal);
595+
return rewriter.create<mlir_ts::NewInterfaceOp>(loc, ifaceType, nullVal, nullInterfaceVTable(nullVal));
560596
}
561597

562598
if (auto classType = dyn_cast<mlir_ts::ClassType>(resType))

‎tslang/include/TypeScript/LowerToLLVM/UnaryBinLogicalOrHelper.h‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -270,24 +270,26 @@ mlir::Value LogicOp(Operation *binOp, SyntaxKind op, mlir::Value left, mlir::Typ
270270
}
271271
else if (isa<mlir_ts::InterfaceType>(leftType))
272272
{
273-
// TODO, extract interface VTable to compare
274-
auto leftVtableValue =
273+
// an interface is the object it is made over: compared by its `this`. A null interface's
274+
// `this` is null whatever vtable it is seen through (the interface's null vtable, whose
275+
// methods throw - mlirGenNullInterfaceVTable - or none at all, zeroed storage)
276+
auto leftThisValue =
275277
left.getDefiningOp<mlir_ts::NullOp>() || left.getDefiningOp<LLVM::ZeroOp>() || matchPattern(left, m_Zero())
276278
? left
277-
: builder.create<mlir_ts::ExtractInterfaceVTableOp>(loc, mlir_ts::OpaqueType::get(leftType.getContext()), left);
278-
auto rightVtableValue =
279+
: builder.create<mlir_ts::ExtractInterfaceThisOp>(loc, mlir_ts::OpaqueType::get(leftType.getContext()), left);
280+
auto rightThisValue =
279281
right.getDefiningOp<mlir_ts::NullOp>() || right.getDefiningOp<LLVM::ZeroOp>() || matchPattern(right, m_Zero())
280282
? right
281-
: builder.create<mlir_ts::ExtractInterfaceVTableOp>(loc, mlir_ts::OpaqueType::get(rightType.getContext()), right);
283+
: builder.create<mlir_ts::ExtractInterfaceThisOp>(loc, mlir_ts::OpaqueType::get(rightType.getContext()), right);
282284

283285
// excluded string
284286
auto intPtrType = llvmtch.getIntPtrType(0);
285287

286-
mlir::Value leftVtableValueAsLLVMType = builder.create<mlir_ts::DialectCastOp>(loc, typeConverter.convertType(leftVtableValue.getType()), leftVtableValue);
287-
mlir::Value rightVtableValueAsLLVMType = builder.create<mlir_ts::DialectCastOp>(loc, typeConverter.convertType(rightVtableValue.getType()), rightVtableValue);
288+
mlir::Value leftThisValueAsLLVMType = builder.create<mlir_ts::DialectCastOp>(loc, typeConverter.convertType(leftThisValue.getType()), leftThisValue);
289+
mlir::Value rightThisValueAsLLVMType = builder.create<mlir_ts::DialectCastOp>(loc, typeConverter.convertType(rightThisValue.getType()), rightThisValue);
288290

289-
mlir::Value leftPtrValue = builder.create<LLVM::PtrToIntOp>(loc, intPtrType, leftVtableValueAsLLVMType);
290-
mlir::Value rightPtrValue = builder.create<LLVM::PtrToIntOp>(loc, intPtrType, rightVtableValueAsLLVMType);
291+
mlir::Value leftPtrValue = builder.create<LLVM::PtrToIntOp>(loc, intPtrType, leftThisValueAsLLVMType);
292+
mlir::Value rightPtrValue = builder.create<LLVM::PtrToIntOp>(loc, intPtrType, rightThisValueAsLLVMType);
291293

292294
auto value = builder.create<StdIOpTy>(loc, v1, leftPtrValue, rightPtrValue);
293295
return value;

‎tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCLinux.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ class MLIRRTTIHelperVCLinux
382382
return mlir::failure();
383383
}
384384

385-
rewriter.create<mlir_ts::GlobalOp>(loc, mth.getOpaqueType(), true, name, LLVM::Linkage::External);
385+
auto globalOp = rewriter.create<mlir_ts::GlobalOp>(loc, mth.getOpaqueType(), true, name, LLVM::Linkage::External);
386+
globalOp->setAttr(EH_TYPE_INFO_ATTR_NAME, rewriter.getUnitAttr());
386387
return mlir::success();
387388
}
388389

‎tslang/include/TypeScript/MLIRLogic/MLIRRTTIHelperVCWin32.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,8 @@ class MLIRRTTIHelperVCWin32
458458
return mlir::failure();
459459
}
460460

461-
rewriter.create<mlir_ts::GlobalOp>(loc, mth.getOpaqueType(), true, name, LLVM::Linkage::External);
461+
auto globalOp = rewriter.create<mlir_ts::GlobalOp>(loc, mth.getOpaqueType(), true, name, LLVM::Linkage::External);
462+
globalOp->setAttr(EH_TYPE_INFO_ATTR_NAME, rewriter.getUnitAttr());
462463
return mlir::success();
463464
}
464465

‎tslang/lib/TypeScript/MLIRGenCast.cpp‎

Lines changed: 101 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,46 +1011,57 @@ namespace mlirgen
10111011
{
10121012
if (auto interfaceType = dyn_cast<mlir_ts::InterfaceType>(type))
10131013
{
1014-
if (auto classType = dyn_cast<mlir_ts::ClassType>(valueType))
1014+
// null (or undefined) is an interface over a null `this`, seen through the interface's null
1015+
// vtable, whose methods throw (mlirGenNullInterfaceVTable). It stays the cast the ownership
1016+
// passes know holds nothing; the cast names the vtable, and the lowering puts it in.
1017+
if (isa<mlir_ts::NullType, mlir_ts::UndefinedType>(valueType))
10151018
{
1016-
auto result = mlirGenPropertyAccessExpression(location, value, VTABLE_NAME, genContext);
1017-
auto vtableAccess = V(result);
1018-
1019-
auto classInfo = getClassInfoByFullName(classType.getName().getValue());
1020-
assert(classInfo);
1021-
1022-
auto implementIndex = classInfo->getImplementIndex(interfaceType.getName().getValue());
1023-
if (implementIndex >= 0)
1019+
auto interfaceInfo = getInterfaceInfoByFullName(interfaceType.getName().getValue());
1020+
if (!interfaceInfo)
10241021
{
1025-
auto interfaceVirtTableIndex = classInfo->implements[implementIndex].virtualIndex;
1026-
1027-
assert(genContext.allowPartialResolve || interfaceVirtTableIndex >= 0);
1028-
1029-
auto interfaceVTablePtr = builder.create<mlir_ts::VTableOffsetRefOp>(
1030-
location, mth.getInterfaceVTableType(interfaceType), vtableAccess, interfaceVirtTableIndex);
1031-
1032-
auto newInterface = builder.create<mlir_ts::NewInterfaceOp>(
1033-
location, mlir::TypeRange{interfaceType}, value, interfaceVTablePtr);
1034-
return V(newInterface);
1022+
return std::nullopt;
10351023
}
10361024

1037-
// create interface vtable from current class
1025+
return mlirGenNullInterfaceVTableCast(location, interfaceType, interfaceInfo, genContext, value);
1026+
}
1027+
1028+
// A class object is a pointer that may be null (`C | null` is stored as `C`), and null
1029+
// stays null: an interface over a null `this`. The vtable may be read through the object,
1030+
// so it is chosen by a test for null: a null object is seen through the interface's null
1031+
// vtable, whose methods throw (mlirGenNullInterfaceVTable). The interface is built over
1032+
// the object outside the test, so that the ownership passes see it is the object (a view),
1033+
// not a value merged from branches.
1034+
if (auto classType = dyn_cast<mlir_ts::ClassType>(valueType))
1035+
{
10381036
auto interfaceInfo = getInterfaceInfoByFullName(interfaceType.getName().getValue());
10391037
assert(interfaceInfo);
10401038

1041-
if (auto createdInterfaceVTableForClass =
1042-
mlirGenCreateInterfaceVTableForClass(location, classInfo, interfaceInfo, genContext))
1043-
{
1044-
LLVM_DEBUG(llvm::dbgs() << "\n!!"
1045-
<< "@ created interface:" << V(createdInterfaceVTableForClass) << "\n";);
1046-
auto newInterface = builder.create<mlir_ts::NewInterfaceOp>(
1047-
location, mlir::TypeRange{interfaceType}, value, createdInterfaceVTableForClass);
1039+
auto nullValue = builder.create<mlir_ts::NullOp>(location, getNullType());
1040+
auto isNotNull = builder.create<mlir_ts::LogicalBinaryOp>(
1041+
location, getBooleanType(), builder.getI32IntegerAttr((int)SyntaxKind::ExclamationEqualsEqualsToken), value, nullValue);
1042+
1043+
auto vtableType = mth.getInterfaceVTableType(interfaceType);
1044+
MLIRCodeLogicHelper mclh(builder, location, compileOptions);
1045+
auto interfaceVTable = mclh.conditionalValue(isNotNull,
1046+
[&]() -> ValueOrLogicalResult {
1047+
auto classVTable = mlirGenClassInterfaceVTable(location, value, classType, interfaceType, genContext);
1048+
EXIT_IF_FAILED_OR_NO_VALUE(classVTable)
1049+
if (V(classVTable).getType() == vtableType)
1050+
{
1051+
return classVTable;
1052+
}
10481053

1049-
return V(newInterface);
1050-
}
1054+
return V(builder.create<mlir_ts::CastOp>(location, vtableType, V(classVTable)));
1055+
},
1056+
[&](mlir::Type) -> ValueOrLogicalResult {
1057+
// null, as the ownership passes know it, naming the null vtable the lowering puts in
1058+
return mlirGenNullInterfaceVTableCast(location, vtableType, interfaceInfo, genContext);
1059+
});
1060+
EXIT_IF_FAILED_OR_NO_VALUE(interfaceVTable)
10511061

1052-
emitError(location) << "type: " << classType.getName() << " missing interface: " << interfaceType.getName();
1053-
return mlir::failure();
1062+
auto newInterface = builder.create<mlir_ts::NewInterfaceOp>(
1063+
location, mlir::TypeRange{interfaceType}, value, V(interfaceVTable));
1064+
return V(newInterface);
10541065
}
10551066

10561067
// tuple to interface
@@ -1097,6 +1108,41 @@ namespace mlirgen
10971108
return std::nullopt;
10981109
}
10991110

1111+
ValueOrLogicalResult MLIRGenImpl::mlirGenClassInterfaceVTable(mlir::Location location, mlir::Value value, mlir_ts::ClassType classType, mlir_ts::InterfaceType interfaceType, const GenContext &genContext)
1112+
{
1113+
auto classInfo = getClassInfoByFullName(classType.getName().getValue());
1114+
assert(classInfo);
1115+
1116+
auto implementIndex = classInfo->getImplementIndex(interfaceType.getName().getValue());
1117+
if (implementIndex >= 0)
1118+
{
1119+
auto result = mlirGenPropertyAccessExpression(location, value, VTABLE_NAME, genContext);
1120+
auto vtableAccess = V(result);
1121+
1122+
auto interfaceVirtTableIndex = classInfo->implements[implementIndex].virtualIndex;
1123+
1124+
assert(genContext.allowPartialResolve || interfaceVirtTableIndex >= 0);
1125+
1126+
return V(builder.create<mlir_ts::VTableOffsetRefOp>(
1127+
location, mth.getInterfaceVTableType(interfaceType), vtableAccess, interfaceVirtTableIndex));
1128+
}
1129+
1130+
// create interface vtable from current class
1131+
auto interfaceInfo = getInterfaceInfoByFullName(interfaceType.getName().getValue());
1132+
assert(interfaceInfo);
1133+
1134+
if (auto createdInterfaceVTableForClass =
1135+
mlirGenCreateInterfaceVTableForClass(location, classInfo, interfaceInfo, genContext))
1136+
{
1137+
LLVM_DEBUG(llvm::dbgs() << "\n!!"
1138+
<< "@ created interface:" << V(createdInterfaceVTableForClass) << "\n";);
1139+
return createdInterfaceVTableForClass;
1140+
}
1141+
1142+
emitError(location) << "type: " << classType.getName() << " missing interface: " << interfaceType.getName();
1143+
return mlir::failure();
1144+
}
1145+
11001146
// An interface value keeps the vtable it was made with, laid out for its own interface. Another
11011147
// interface it is cast to reads that vtable by its own slot numbers, which match only when it is the
11021148
// value's interface or one it extends (whose members come first): `interface HasDist { dist }` read
@@ -1644,6 +1690,30 @@ namespace mlirgen
16441690
return std::nullopt;
16451691
}
16461692

1693+
// `C | null` given to `I | null`: both are stored as their one member (null is a null pointer
1694+
// in it), so this is the class-to-interface cast of `C` to `I`. A plain cast read the class
1695+
// pointer as an interface.
1696+
if (auto srcUnionType = dyn_cast<mlir_ts::UnionType>(valueType); srcUnionType && isa<mlir_ts::InterfaceType>(baseType))
1697+
{
1698+
mlir::Type srcBaseType;
1699+
if (mth.isUnionTypeNeedsTag(location, srcUnionType, srcBaseType) || !isa_and_nonnull<mlir_ts::ClassType>(srcBaseType))
1700+
{
1701+
return std::nullopt;
1702+
}
1703+
1704+
auto member = builder.create<mlir_ts::GetValueFromUnionOp>(location, srcBaseType, value);
1705+
CAST_A(casted, location, baseType, member, genContext);
1706+
return V(builder.create<mlir_ts::CastOp>(location, type, casted));
1707+
}
1708+
1709+
// null given to `I | null` is the null interface, as null given to `I` is (castToInterfaceSpecialCases)
1710+
if (isa<mlir_ts::NullType, mlir_ts::UndefinedType>(valueType) && isa<mlir_ts::InterfaceType>(baseType))
1711+
{
1712+
auto casted = cast(location, baseType, value, genContext, /*disableStrictNullCheck=*/true);
1713+
EXIT_IF_FAILED_OR_NO_VALUE(casted)
1714+
return V(builder.create<mlir_ts::CastOp>(location, type, V(casted)));
1715+
}
1716+
16471717
if (isa<mlir_ts::NullType>(valueType) || isa<mlir_ts::UndefinedType>(valueType) || isa<mlir_ts::AnyType>(valueType)
16481718
|| isa<mlir_ts::OptionalType>(valueType) || isa<mlir_ts::UnionType>(valueType))
16491719
{

‎tslang/lib/TypeScript/MLIRGenFunctions.cpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,9 +1948,12 @@ namespace mlirgen
19481948
return mlir::failure();
19491949
}
19501950

1951-
// add exit code
1951+
// add exit code: a function returning nothing declares no return variable of its own, and one
1952+
// made while another function is being generated (a null interface's method stub for a cast
1953+
// in it) finds that function's instead
19521954
auto retVarInfo = symbolTable.lookup(RETURN_VARIABLE_NAME);
1953-
if (retVarInfo.first)
1955+
auto *retVarDef = retVarInfo.first ? retVarInfo.first.getDefiningOp() : nullptr;
1956+
if (retVarDef && funcOp->isProperAncestor(retVarDef))
19541957
{
19551958
builder.create<mlir_ts::ExitOp>(location, retVarInfo.first);
19561959
}

0 commit comments

Comments
 (0)