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
4 changes: 3 additions & 1 deletion tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2795,7 +2795,9 @@ struct NewArrayOpLowering : public TsLlvmPattern<mlir_ts::NewArrayOp>
auto multSizeOfTypeValue =
rewriter.create<LLVM::MulOp>(loc, llvmIndexType, ValueRange{sizeOfTypeValue, countAsIndexType});

auto allocated = ch.MemoryAlloc(multSizeOfTypeValue);
// zeroed: its elements are not set yet, and under `-mm=rc` the first store into one
// releases what the slot held - `malloc`'s leftovers would be released as a reference
auto allocated = ch.MemoryAlloc(multSizeOfTypeValue, MemoryAllocSet::Zero);

// create array type
auto llvmRtArrayStructType = tch.convertType(arrayType);
Expand Down
136 changes: 89 additions & 47 deletions tslang/lib/TypeScript/MLIRGenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -928,64 +928,25 @@ namespace mlirgen
}

auto constArrayType = dyn_cast<mlir_ts::ConstArrayType>(valueType);

// A `const` array literal (`const c = [1, 2]`) now gets real identity
// storage so mutating methods like .sort() share one heap array instead of
// a fresh disposable copy per call (see processConstRef /
// adjustLocalVariableType / adjustGlobalVariableType in MLIRGenImpl.h) --
// which means reading `c` elsewhere yields the already-widened
// `!ts.array<T>`, not the original `!ts.const_array<T,N>` this function
// keys off. Trace back through whichever shape that widening introduced to
// recover the original literal, so the elementwise conversion below still
// applies to a `const` int-array literal read into e.g. `number[]`:
// - local: Load -> VariableOp -> its initializer (a Cast chain)
// - module-level: Load -> AddressOf(@global) -> that GlobalOp's
// initializer region, whose GlobalResultOp terminator yields the same
// kind of Cast chain (see createGlobalVariableInitialization)
mlir::Value literalValue = value;
if (!constArrayType)
{
if (auto loadOp = value.getDefiningOp<mlir_ts::LoadOp>())
{
if (auto varOp = loadOp.getReference().getDefiningOp<mlir_ts::VariableOp>())
{
if (auto init = varOp.getInitializer())
{
literalValue = init;
}
}
else if (auto addressOfOp = loadOp.getReference().getDefiningOp<mlir_ts::AddressOfOp>())
{
if (auto globalOp = theModule.lookupSymbol<mlir_ts::GlobalOp>(addressOfOp.getGlobalNameAttr()))
{
if (auto *initBlock = globalOp.getInitializerBlock())
{
if (auto globalResultOp = dyn_cast<mlir_ts::GlobalResultOp>(initBlock->getTerminator()))
{
if (globalResultOp.getResults().size() == 1)
{
literalValue = globalResultOp.getResults().front();
}
}
}
}
}
}

while (auto castOp = literalValue.getDefiningOp<mlir_ts::CastOp>())
// a `const c = [1, 2]` read back is already the array its storage widened it to (see
// processConstRef / adjustLocalVariableType / adjustGlobalVariableType in MLIRGenImpl.h),
// and may have been changed since: its elements are converted as they are now
if (auto srcArrayType = dyn_cast<mlir_ts::ArrayType>(valueType))
{
literalValue = castOp.getIn();
return castArrayElementwise(location, arrayType, value, srcArrayType, genContext);
}

constArrayType = dyn_cast<mlir_ts::ConstArrayType>(literalValue.getType());
return std::nullopt;
}

if (!constArrayType || constArrayType.getElementType() == arrayType.getElementType())
if (constArrayType.getElementType() == arrayType.getElementType())
{
return std::nullopt;
}

auto constOp = literalValue.getDefiningOp<mlir_ts::ConstantOp>();
auto constOp = value.getDefiningOp<mlir_ts::ConstantOp>();
auto elementAttrs = constOp ? dyn_cast<mlir::ArrayAttr>(constOp.getValue()) : mlir::ArrayAttr();
if (!elementAttrs)
{
Expand Down Expand Up @@ -1035,6 +996,87 @@ namespace mlirgen
return V(builder.create<mlir_ts::CreateArrayOp>(location, arrayType, elements));
}

// An array of numbers held as `s32` (or another integer or float type) keeps that layout, so it is
// not shared as a `number[]`: a new array is built, each element cast to the element type as it is
// assigned - nested arrays in turn. The new array is a copy, so later changes to one of them are not
// seen through the other. An array literal met at a union or a `const` binding gets here with s32
// elements (see castConstArrayToArray), and so does a variable holding one.
std::optional<ValueOrLogicalResult> MLIRGenImpl::castArrayElementwise(mlir::Location location, mlir_ts::ArrayType arrayType, mlir::Value value, mlir_ts::ArrayType srcArrayType, const GenContext &genContext)
{
auto isNumeric = [](mlir::Type type) {
return isa<mlir::IntegerType>(type) || isa<mlir::FloatType>(type) || isa<mlir_ts::NumberType>(type);
};

std::function<bool(mlir::Type)> isNumericSource = [&](mlir::Type type) {
if (auto nestedArrayType = dyn_cast<mlir_ts::ArrayType>(type))
{
return isNumericSource(nestedArrayType.getElementType());
}

return isa<mlir::IntegerType>(type) || isa<mlir::FloatType>(type);
};

// what a number can be assigned to: another number type, `any`, or a union holding a number
std::function<bool(mlir::Type)> isNumericTarget = [&](mlir::Type type) {
if (auto nestedArrayType = dyn_cast<mlir_ts::ArrayType>(type))
{
return isNumericTarget(nestedArrayType.getElementType());
}

if (auto unionType = dyn_cast<mlir_ts::UnionType>(type))
{
return llvm::any_of(unionType.getTypes(), isNumeric);
}

return isNumeric(type) || isa<mlir_ts::AnyType>(type) || isa<mlir_ts::UnknownType>(type);
};

auto srcElementType = srcArrayType.getElementType();
auto elementType = arrayType.getElementType();
if (srcElementType == elementType || !isNumericSource(srcElementType) || !isNumericTarget(elementType))
{
return std::nullopt;
}

SymbolTableScopeT varScope(symbolTable);

auto length = builder.create<mlir_ts::LengthOfOp>(location, builder.getIndexType(), value);
auto newArray = builder.create<mlir_ts::NewArrayOp>(location, arrayType, length);

auto srcArrayVarDecl = std::make_shared<VariableDeclarationDOM>(".src_array", srcArrayType, location);
DECLARE(srcArrayVarDecl, value);

auto dstArrayVarDecl = std::make_shared<VariableDeclarationDOM>(".dst_array", arrayType, location);
DECLARE(dstArrayVarDecl, newArray);

NodeFactory nf(NodeFactoryFlags::None);

auto _src_array_ident = nf.createIdentifier(S(".src_array"));
auto _dst_array_ident = nf.createIdentifier(S(".dst_array"));
auto _i = nf.createIdentifier(S(".i"));

// for (let .i = 0; .i < .src_array.length; ++.i) .dst_array[.i] = .src_array[.i];
NodeArray<VariableDeclaration> declarations;
declarations.push_back(nf.createVariableDeclaration(_i, undefined, undefined, nf.createNumericLiteral(S("0"))));
auto initVars = nf.createVariableDeclarationList(declarations, NodeFlags::Let);

auto cond = nf.createBinaryExpression(_i, nf.createToken(SyntaxKind::LessThanToken),
nf.createPropertyAccessExpression(_src_array_ident, nf.createIdentifier(S(LENGTH_FIELD_NAME))));

auto incr = nf.createPrefixUnaryExpression(nf.createToken(SyntaxKind::PlusPlusToken), _i);

auto assign = nf.createExpressionStatement(
nf.createBinaryExpression(nf.createElementAccessExpression(_dst_array_ident, _i), nf.createToken(SyntaxKind::EqualsToken),
nf.createElementAccessExpression(_src_array_ident, _i)));

if (mlir::failed(mlirGen(nf.createForStatement(initVars, cond, incr, assign), genContext)))
{
return mlir::failure();
}

return V(newArray);
}

std::optional<ValueOrLogicalResult> MLIRGenImpl::castTupleLikeVariants(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext)
{
// const tuple to tuple
Expand Down
1 change: 1 addition & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -11121,6 +11121,7 @@ class MLIRGenImpl

// constant array literal to an array of another element type, element by element
std::optional<ValueOrLogicalResult> castConstArrayToArray(mlir::Location location, mlir::Type type, mlir::Value value, mlir::Type valueType, const GenContext &genContext);
std::optional<ValueOrLogicalResult> castArrayElementwise(mlir::Location location, mlir_ts::ArrayType arrayType, mlir::Value value, mlir_ts::ArrayType srcArrayType, const GenContext &genContext);

// reports which part of a function signature does not match the one it is assigned to
void emitFunctionTypeMismatch(mlir::Location location, mlir::Type valueType, mlir::Type type, MatchResult match);
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ tslang_add_test(NAME test-compile-00-typeof-function-narrowing COMMAND test-runn
tslang_add_test(NAME test-compile-00-typeof-static-fold-conditions COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts")
tslang_add_test(NAME test-compile-00-typeof-union-narrowing COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_union_narrowing.ts")
tslang_add_test(NAME test-compile-00-const-array-to-array-elements COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_to_array_elements.ts")
tslang_add_test(NAME test-compile-00-array-widen-elements-now COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_widen_elements_now.ts")
tslang_add_test(NAME test-compile-00-const-array-nested-elements COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_nested_elements.ts")
tslang_add_test(NAME test-compile-00-const-array-mutation COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_mutation.ts")
tslang_add_test(NAME test-compile-00-array-move-wide-elements COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts")
Expand Down Expand Up @@ -731,6 +732,7 @@ tslang_add_test(NAME test-jit-00-typeof-function-narrowing COMMAND test-runner -
tslang_add_test(NAME test-jit-00-typeof-static-fold-conditions COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_static_fold_conditions.ts")
tslang_add_test(NAME test-jit-00-typeof-union-narrowing COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00typeof_union_narrowing.ts")
tslang_add_test(NAME test-jit-00-const-array-to-array-elements COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_to_array_elements.ts")
tslang_add_test(NAME test-jit-00-array-widen-elements-now COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_widen_elements_now.ts")
tslang_add_test(NAME test-jit-00-const-array-nested-elements COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_nested_elements.ts")
tslang_add_test(NAME test-jit-00-const-array-mutation COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_mutation.ts")
tslang_add_test(NAME test-jit-00-array-move-wide-elements COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_move_wide_elements.ts")
Expand Down Expand Up @@ -1477,6 +1479,7 @@ set(TSLANG_CORPUS
00conditional_owned_result.ts
00const_array_nested_elements.ts
00const_array_to_array_elements.ts
00array_widen_elements_now.ts
00owned_array_splice.ts
00alloc_in_catch.ts
00any_compare.ts
Expand Down
73 changes: 73 additions & 0 deletions tslang/test/tester/tests/00array_widen_elements_now.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// An integer array literal keeps its `s32` elements, and an array held in a variable is converted
// element by element when it is read as `number[]`. The conversion used to trace the variable back
// to the literal it was declared with and build the array from that literal, so everything done to
// the array since - push, element writes, a new array assigned, a sort - was silently lost.
const moduleArray = [7, 8, 9];
moduleArray[0] = 70;
moduleArray.push(10);
const moduleWidened: number[] = moduleArray;

function widen(a: s32[]): number[] {
return a;
}

function main() {
let pushed = [1, 2];
pushed.push(3);
pushed[0] = 9;
const fromLet: number[] = pushed;
assert(fromLet.length == 3, "let: pushed element counted");
assert(fromLet[0] == 9, "let: written element");
assert(fromLet[2] / 2 == 1.5, "let: pushed element, number arithmetic");

const written = [3, 1, 2];
written[0] = 42;
const fromConst: number[] = written;
assert(fromConst[0] == 42, "const: written element");
assert(fromConst[1] == 1, "const: untouched element");

let reassigned = [1, 2];
reassigned = [5, 6, 7];
const fromReassigned: number[] = reassigned;
assert(fromReassigned.length == 3, "reassigned: length");
assert(fromReassigned[0] == 5, "reassigned: element");

// no literal to trace back to at all: a parameter
const fromParameter = widen(pushed);
assert(fromParameter.length == 3, "parameter: length");
assert(fromParameter[0] / 2 == 4.5, "parameter: element, number arithmetic");

const nested = [[1, 2], [3]];
const fromNested: number[][] = nested;
assert(fromNested.length == 2, "nested: outer length");
assert(fromNested[1].length == 1, "nested: inner length");
assert(fromNested[0][1] / 4 == 0.5, "nested: element, number arithmetic");

const toUnion: (number | string)[] = written;
assert(toUnion[0] == 42, "union element type");

const toAny: any[] = written;
assert(toAny.length == 3, "any element type: length");

const empty: s32[] = [];
const fromEmpty: number[] = empty;
assert(fromEmpty.length == 0, "empty array");

// the widened array is a copy: a write to it does not reach the original
const copy: number[] = written;
copy[1] = 100;
assert(written[1] == 1, "copy: original unchanged");
assert(copy[1] == 100, "copy: written");

// module level: widened in the global initializer, after the writes before it
assert(moduleWidened.length == 4, "module: length");
assert(moduleWidened[0] == 70, "module: written element");
assert(moduleWidened[3] / 4 == 2.5, "module: pushed element, number arithmetic");

// module level: widened here, after a write made in this function
moduleArray[1] = 80;
const moduleLater: number[] = moduleArray;
assert(moduleLater[1] == 80, "module: written in function");

print("done.");
}
7 changes: 2 additions & 5 deletions tslang/test/tester/tests/00const_array_to_array_elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ function lengthOf(u: number[] | string) {
// `const c` case below, but through the global-variable codegen path (createGlobalVariable /
// adjustGlobalVariableType), not the local one (createLocalVariable / adjustLocalVariableType).
// Giving `const` array literals real identity storage (so mutating methods like .sort() share one
// heap array - see docs/const-let-storage-design.md) initially broke this: reading `moduleConst`
// back yields `AddressOf(@moduleConst) -> Load`, a shape castConstArrayToArray's local-only
// trace-back (Load -> VariableOp -> initializer) did not recognize, so it fell through to the
// generic array-to-array cast and was rejected ("element type number is not base of type s32").
// Fixed by also unwrapping AddressOf(global) -> the GlobalOp's own initializer terminator.
// heap array - see docs/const-let-storage-design.md) means reading `moduleConst` back yields an
// `s32[]` loaded from the global, not the literal; castArrayElementwise converts what it holds.
const moduleConst = [7, 8, 9];

function takesNumberArray(a: number[]) {
Expand Down
Loading