Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
0cd3dc2
-mm=own: spec section 22, strings as values (design)
ASDAlexander77 Oct 2, 2026
a79fa9b
-mm=own: the strings-as-values implementation plan (spec section 22)
ASDAlexander77 Oct 2, 2026
d07f620
-mm=own strings: ts.StringCopy and the facts that know it (spec 22.2)
ASDAlexander77 Oct 2, 2026
308c2a1
-mm=own strings: isCopyableString needs a tag-free union; StringCopy …
ASDAlexander77 Oct 2, 2026
9d0ec2d
-mm=own strings: the inference pass's dry run (spec 22.3), no change yet
ASDAlexander77 Oct 2, 2026
4ee1180
-mm=own strings: a second reference to a string own cannot prove beco…
ASDAlexander77 Oct 2, 2026
edcee2a
-mm=own strings: a copy only for a retain in its use's block; the pus…
ASDAlexander77 Oct 2, 2026
efd252b
-mm=own strings: mixed returns, corpus and results (spec 22.7)
ASDAlexander77 Oct 2, 2026
09c8747
-mm=own strings: the Linux run (spec 22.7)
ASDAlexander77 Oct 2, 2026
a5fc162
-mm=own strings: the copies see the dry run's borrow attributes
ASDAlexander77 Oct 2, 2026
43d0cb4
-mm=own strings: a test that the copy is made in the store's block
ASDAlexander77 Oct 2, 2026
39f5b74
-mm=own strings: the copy's-block check on a file of its own
ASDAlexander77 Oct 2, 2026
de6e8f2
-mm=own strings: no dangling dominance; a copy at the retain only bef…
ASDAlexander77 Oct 2, 2026
4515a68
-mm=own strings: the final review's doc points (spec 22.1, 22.2, 22.7…
ASDAlexander77 Oct 2, 2026
2345904
-mm=own strings: when a closure owns its copies, as the code decides …
ASDAlexander77 Oct 2, 2026
67d5383
-mm=own strings: the Linux run after the final review's fixes (spec 2…
ASDAlexander77 Oct 2, 2026
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
1,051 changes: 1,051 additions & 0 deletions tslang/docs/superpowers/plans/2026-10-02-own-strings-as-values.md

Large diffs are not rendered by default.

354 changes: 353 additions & 1 deletion tslang/docs/superpowers/specs/2026-09-24-own-memory-model-design.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions tslang/include/TypeScript/TypeScriptOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2373,6 +2373,15 @@ def TypeScript_StringConcatOp : TypeScript_Op<"StringConcat"> {
let results = (outs Res<TypeScript_String, "", [MemAlloc]>:$result);
}

// A copy of a string: a new block holding the same text, null when `in` is null (or, for an
// optional, when it holds no value). Made by -mm=own's inference pass only, where a second
// reference to a string cannot be proven a move or a borrow (spec 22); it lowers under every
// model.
def TypeScript_StringCopyOp : TypeScript_Op<"StringCopy", [AllTypesMatch<["in", "result"]>]> {
let arguments = (ins AnyType:$in);
let results = (outs Res<AnyType, "", [MemAlloc]>:$result);
}

def TypeScript_StringCompareOp : TypeScript_Op<"StringCompare"> {
let arguments = (ins TypeScript_String:$op1, TypeScript_String:$op2, I32Attr:$code);
let results = (outs TypeScript_Boolean:$result);
Expand Down
2 changes: 1 addition & 1 deletion tslang/lib/TypeScript/LowerToAffineLoops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2657,7 +2657,7 @@ void AddTsAffineLegalOps(ConversionTarget &target)
mlir_ts::ElementRefOp, mlir_ts::PointerOffsetRefOp, mlir_ts::FuncOp, mlir_ts::GlobalOp, mlir_ts::GlobalResultOp, mlir_ts::DefaultOp,
mlir_ts::HasValueOp, mlir_ts::ValueOp, mlir_ts::ValueOrDefaultOp, mlir_ts::NullOp, mlir_ts::ParseFloatOp, mlir_ts::ParseIntOp, mlir_ts::IsNaNOp,
mlir_ts::PrintOp, mlir_ts::ConvertFOp, mlir_ts::SizeOfOp, mlir_ts::TypeDescriptorOp, mlir_ts::RetainOp, mlir_ts::ReleaseOp, mlir_ts::RetainSlotOp, mlir_ts::ReleaseSlotOp, mlir_ts::RetainCellOp, mlir_ts::ReleaseCellOp, mlir_ts::StoreOp, mlir_ts::SymbolRefOp, mlir_ts::LengthOfOp, mlir_ts::SetLengthOfOp,
mlir_ts::StringLengthOp, mlir_ts::StringResizeOp, mlir_ts::StringConcatOp,mlir_ts::StringCompareOp, mlir_ts::AnyCompareOp,
mlir_ts::StringLengthOp, mlir_ts::StringResizeOp, mlir_ts::StringConcatOp, mlir_ts::StringCopyOp, mlir_ts::StringCompareOp, mlir_ts::AnyCompareOp,
mlir_ts::LoadOp, mlir_ts::LoadSaveOp, mlir_ts::NewOp, mlir_ts::CreateTupleOp, mlir_ts::DeconstructTupleOp, mlir_ts::CreateArrayOp,
mlir_ts::NewEmptyArrayOp, mlir_ts::NewArrayOp, mlir_ts::DeleteOp, mlir_ts::PropertyRefOp, mlir_ts::InsertPropertyOp,
mlir_ts::ExtractPropertyOp, mlir_ts::LogicalBinaryOp, mlir_ts::UndefOp, mlir_ts::VariableOp, mlir_ts::DebugVariableOp, mlir_ts::AllocaOp,
Expand Down
67 changes: 66 additions & 1 deletion tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,71 @@ class StringConcatOpLowering : public TsLlvmPattern<mlir_ts::StringConcatOp>
}
};

// A copy of a string: `strlen + 1` bytes, from the allocator `ts.StringConcat` uses. A null string
// stays null and allocates nothing. An optional `{ptr, i1}` is copied only when it holds a value:
// a pointer whose flag says "none" may be anything.
class StringCopyOpLowering : public TsLlvmPattern<mlir_ts::StringCopyOp>
{
public:
using TsLlvmPattern<mlir_ts::StringCopyOp>::TsLlvmPattern;

LogicalResult matchAndRewrite(mlir_ts::StringCopyOp op, Adaptor transformed,
ConversionPatternRewriter &rewriter) const final
{
TypeHelper th(rewriter);
LLVMCodeHelper ch(op, rewriter, getTypeConverter(), tsLlvmContext->compileOptions);
TypeConverterHelper tch(getTypeConverter());
CodeLogicHelper clh(op, rewriter);

auto loc = op->getLoc();
auto i8PtrTy = th.getPtrType();
auto llvmIndexType = tch.convertType(th.getIndexType());
auto llvmBoolType = th.getLLVMBoolType();
auto strlenFuncOp = ch.getOrInsertFunction("strlen", th.getFunctionType(llvmIndexType, {i8PtrTy}));

auto copyOf = [&](mlir::Value source, mlir::Value copyIt) {
return clh.conditionalExpressionLowering(
loc, i8PtrTy, copyIt,
[&](OpBuilder &, Location) -> mlir::Value {
mlir::Value bytes = rewriter.create<LLVM::CallOp>(loc, strlenFuncOp, ValueRange{source}).getResult();
bytes = rewriter.create<LLVM::AddOp>(
loc, llvmIndexType,
ValueRange{bytes, rewriter.create<LLVM::ConstantOp>(loc, llvmIndexType, rewriter.getIntegerAttr(llvmIndexType, 1))});
auto copy = ch.MemoryAlloc(bytes);
rewriter.create<LLVM::MemcpyOp>(loc, copy, source, bytes, /*isVolatile=*/false);
return copy;
},
[&](OpBuilder &, Location) -> mlir::Value { return source; });
};

mlir::Value in = transformed.getIn();
auto isSet = [&](mlir::Value pointer) -> mlir::Value {
return rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, pointer, rewriter.create<LLVM::ZeroOp>(loc, i8PtrTy));
};

if (mlir::isa<LLVM::LLVMPointerType>(in.getType()))
{
rewriter.replaceOp(op, ValueRange{copyOf(in, isSet(in))});
return success();
}

// an optional: `{ptr, i1}`
auto optionalType = mlir::dyn_cast<LLVM::LLVMStructType>(in.getType());
if (!optionalType || optionalType.getBody().size() != 2 ||
!mlir::isa<LLVM::LLVMPointerType>(optionalType.getBody()[0]) || !optionalType.getBody()[1].isInteger(1))
{
return rewriter.notifyMatchFailure(op, "ts.StringCopy takes a pointer or a { ptr, i1 } optional");
}

auto pointer = rewriter.create<LLVM::ExtractValueOp>(loc, in, ArrayRef<int64_t>{0});
auto hasValue = rewriter.create<LLVM::ExtractValueOp>(loc, in, ArrayRef<int64_t>{1});
auto copyIt = rewriter.create<LLVM::AndOp>(loc, llvmBoolType, hasValue, isSet(pointer));
auto copied = copyOf(pointer, copyIt);
rewriter.replaceOp(op, ValueRange{rewriter.create<LLVM::InsertValueOp>(loc, in, copied, ArrayRef<int64_t>{0})});
return success();
}
};

class StringCompareOpLowering : public TsLlvmPattern<mlir_ts::StringCompareOp>
{
public:
Expand Down Expand Up @@ -7963,7 +8028,7 @@ void TypeScriptToLLVMLoweringPass::runOnOperation()
DeconstructTupleOpLowering, CreateArrayOpLowering, NewEmptyArrayOpLowering, NewArrayOpLowering, ArrayPushOpLowering,
ArrayPopOpLowering, ArrayUnshiftOpLowering, ArrayShiftOpLowering, ArraySpliceOpLowering, ArrayViewOpLowering, DeleteOpLowering,
ParseFloatOpLowering, ParseIntOpLowering, IsNaNOpLowering, PrintOpLowering, ConvertFOpLowering, StoreOpLowering, SizeOfOpLowering, TypeDescriptorOpLowering, RetainOpLowering, ReleaseOpLowering, RetainSlotOpLowering, ReleaseSlotOpLowering, RetainCellOpLowering, ReleaseCellOpLowering,
InsertPropertyOpLowering, LengthOfOpLowering, SetLengthOfOpLowering, StringLengthOpLowering, StringResizeOpLowering, StringConcatOpLowering,
InsertPropertyOpLowering, LengthOfOpLowering, SetLengthOfOpLowering, StringLengthOpLowering, StringResizeOpLowering, StringConcatOpLowering, StringCopyOpLowering,
StringCompareOpLowering, AnyCompareOpLowering, CharToStringOpLowering, UndefOpLowering, CopyStructOpLowering, MemoryCopyOpLowering, MemoryMoveOpLowering,
LoadSaveValueLowering, ThrowUnwindOpLowering, ThrowCallOpLowering, VariableOpLowering, DebugVariableOpLowering, AllocaOpLowering, InvokeOpLowering,
InvokeHybridOpLowering, VirtualSymbolRefOpLowering, ThisVirtualSymbolRefOpLowering, InterfaceSymbolRefOpLowering,
Expand Down
40 changes: 39 additions & 1 deletion tslang/lib/TypeScript/OwnershipFacts.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ inline bool isFresh(mlir::Value value)
}

if (mlir::isa<mlir_ts::NewOp, mlir_ts::CreateArrayOp, mlir_ts::NewArrayOp, mlir_ts::StringConcatOp,
mlir_ts::StringResizeOp, mlir_ts::CharToStringOp>(def))
mlir_ts::StringResizeOp, mlir_ts::StringCopyOp, mlir_ts::CharToStringOp>(def))
{
return true;
}
Expand Down Expand Up @@ -433,6 +433,44 @@ inline bool holdsNoBlock(mlir::Value value)
return false;
}

// A value -mm=own copies rather than give a second owner (spec 22.1): a string, a string literal,
// a union of strings and null that needs no tag (one pointer), or an optional of one of those
// (`{ptr, i1}`).
inline bool isCopyableString(mlir::Type type)
{
auto isText = [](mlir::Type member) {
if (auto literalType = mlir::dyn_cast<mlir_ts::LiteralType>(member))
{
return mlir::isa<mlir_ts::StringType>(literalType.getElementType());
}

return mlir::isa<mlir_ts::StringType>(member);
};

if (auto optionalType = mlir::dyn_cast<mlir_ts::OptionalType>(type))
{
return !mlir::isa<mlir_ts::OptionalType>(optionalType.getElementType()) &&
isCopyableString(optionalType.getElementType());
}

if (auto unionType = mlir::dyn_cast<mlir_ts::UnionType>(type))
{
if (!llvm::any_of(unionType.getTypes(), isText) ||
!llvm::all_of(unionType.getTypes(), [&](mlir::Type member) {
return isText(member) || mlir::isa<mlir_ts::NullType>(member);
}))
{
return false;
}

// only a union that lowers to one pointer: a tagged union is a struct
MLIRTypeHelper mth(type.getContext(), CompileOptions{});
return !mth.isUnionTypeNeedsTag(mlir::UnknownLoc::get(type.getContext()), unionType);
}

return isText(type);
}

// Some view of `value` is a `ts.NewInterface`, which boxes what it shows into a block.
inline bool boxedByView(mlir::Value value)
{
Expand Down
Loading
Loading