From b5fc51dbb87fd689669a4ad4f2a569e5331a85e7 Mon Sep 17 00:00:00 2001 From: Goktug Gokdogan Date: Fri, 9 Oct 2026 00:09:04 -0700 Subject: [PATCH 1/5] Lower constant array.new_data in OptimizeInstructions array.new_data is not a constant expression, which blocks optimizations such as hoisting array literals into immutable globals, and passes like Precompute, Heap2Local, GUFA and SimplifyGlobals don't understand it. When the offset and size are constant and in bounds of a passive segment, lower it to: * array.new_default, if all elements are zero * array.new, if all elements are equal * array.new_fixed, if there are at most 64 elements Non-empty slices are only lowered with --traps-never-happen, since array.new_data traps if the segment was dropped. An empty slice at offset 0 can never trap, so it is always lowered. The 64-element limit exists because array.new_fixed costs a full *.const per element, while a data segment stores raw bytes. On large J2Wasm applications this removes over 99.7% of passive data segments and reduces binary size by ~1.8% (~1.3% gzipped). --- src/passes/OptimizeInstructions.cpp | 102 +++ src/wasm-interpreter.h | 10 +- .../optimize-instructions-array-new-data.wast | 758 ++++++++++++++++++ 3 files changed, 863 insertions(+), 7 deletions(-) create mode 100644 test/lit/passes/optimize-instructions-array-new-data.wast diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index c4e50e8f5e0..193128e92b3 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2349,6 +2349,108 @@ struct OptimizeInstructions replaceCurrent(block); } + void visitArrayNewData(ArrayNewData* curr) { + // Lower array.new_data with constant operands to array.new_default, + // array.new or array.new_fixed. Unlike array.new_data, those are constant + // expressions, and more optimizations understand them. + if (curr->type == Type::unreachable) { + return; + } + + auto* offsetConst = curr->offset->dynCast(); + auto* sizeConst = curr->size->dynCast(); + if (!offsetConst || !sizeConst) { + return; + } + + auto* seg = getModule()->getDataSegment(curr->segment); + if (!seg->isPassive()) { + return; + } + + auto heapType = curr->type.getHeapType(); + const auto& element = heapType.getArray().element; + if (!element.type.isNumber()) { + return; + } + + uint64_t offset = offsetConst->value.getUnsigned(); + uint64_t size = sizeConst->value.getUnsigned(); + uint64_t elemBytes = element.getByteSize(); + // offset and size are u32 and elemBytes <= 16, so this can't overflow. + uint64_t end = offset + size * elemBytes; + if (end > seg->data.size()) { + // This will trap. + return; + } + + // A dropped segment acts as if it had length 0, so unless end == 0 this + // traps if the segment was dropped at runtime, which we can't rule out. + // TODO: Optimize without TNH if the segment is never dropped. + if (end > 0 && !getPassOptions().trapsNeverHappen) { + return; + } + + Builder builder(*getModule()); + + if (size == 0) { + replaceCurrent(builder.makeArrayNewFixed(heapType, {})); + return; + } + + auto readElement = [&](Index i) -> Literal { + const auto* src = &seg->data[offset + i * elemBytes]; + switch (element.packedType) { + case Field::NotPacked: + return Literal::makeFromMemory(src, element.type); + // Packed values are truncated when stored, so the extension doesn't + // matter; sign-extending gives the shorter encoding (-1 vs 255). + case Field::i8: + return Literal(int32_t(int8_t(*src))); + case Field::i16: + return Literal(int32_t(Bits::readLE(src))); + } + WASM_UNREACHABLE("unexpected packed type"); + }; + + // If all elements are equal, array.new(_default) is compact at any size. + auto first = readElement(0); + bool allEqual = true; + for (Index i = 1; i < size; i++) { + if (readElement(i) != first) { + allEqual = false; + break; + } + } + if (allEqual) { + auto* sizeExpr = builder.makeConst(int32_t(size)); + // Literal comparison is bitwise, so e.g. -0.0 is not considered zero. + if (first == Literal::makeZero(element.type)) { + replaceCurrent(builder.makeArrayNew(heapType, sizeExpr)); + } else { + // visitArrayNew turns the size == 1 case into array.new_fixed. + replaceCurrent( + builder.makeArrayNew(heapType, sizeExpr, builder.makeConst(first))); + } + return; + } + + // array.new_fixed needs a full *.const per element while the segment + // stores raw bytes, so only do this for small arrays. Empirically, 64 sits + // in a natural gap between small/medium array literals (2D array rows, + // enum/switch tables, etc.) and large bulk lookup tables (100+ elements). + constexpr uint64_t MaxArrayNewFixedSize = 64; + if (size > MaxArrayNewFixedSize) { + return; + } + std::vector values; + values.reserve(size); + for (Index i = 0; i < size; i++) { + values.push_back(builder.makeConst(readElement(i))); + } + replaceCurrent(builder.makeArrayNewFixed(heapType, values)); + } + void visitArrayGet(ArrayGet* curr) { skipNonNullCast(curr->ref, curr); trapOnNull(curr, curr->ref); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d5e358a081d..ceb3a794909 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4747,11 +4747,9 @@ class ModuleRunnerBase : public ExpressionRunner { const auto& seg = *wasm.getDataSegment(curr->segment); auto elemBytes = element.getByteSize(); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" - - uint64_t end; - if (std::ckd_add(&end, offset, size * elemBytes) || end > seg.data.size()) { + // offset and size are u32 and elemBytes <= 16, so this can't overflow. + uint64_t end = offset + size * elemBytes; + if (end > seg.data.size()) { trap("out of bounds segment access in array.new_data"); } if (droppedDataSegments.contains(curr->segment) && end > 0) { @@ -4762,8 +4760,6 @@ class ModuleRunnerBase : public ExpressionRunner { memcpy(contents.data(), &seg.data[offset], size * elemBytes); } -#pragma GCC diagnostic pop - return self()->makeGCData(std::move(contents), curr->type); } Flow visitArrayNewElem(ArrayNewElem* curr) { diff --git a/test/lit/passes/optimize-instructions-array-new-data.wast b/test/lit/passes/optimize-instructions-array-new-data.wast new file mode 100644 index 00000000000..8f601d813cd --- /dev/null +++ b/test/lit/passes/optimize-instructions-array-new-data.wast @@ -0,0 +1,758 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --optimize-instructions --traps-never-happen -all -S -o - | filecheck %s --check-prefix TNH +;; RUN: wasm-opt %s --optimize-instructions -all -S -o - | filecheck %s --check-prefix NO_TNH + +(module + ;; TNH: (type $arr-i8 (array (mut i8))) + + ;; TNH: (type $arr-i32 (array (mut i32))) + + ;; TNH: (type $arr-f32 (array (mut f32))) + + ;; TNH: (type $arr-i16 (array (mut i16))) + + ;; TNH: (type $arr-i64 (array (mut i64))) + + ;; TNH: (type $arr-f64 (array (mut f64))) + + ;; TNH: (type $arr-v128 (array (mut v128))) + + ;; TNH: (import "a" "b" (func $get-i32 (type $8) (result i32))) + ;; NO_TNH: (type $arr-i8 (array (mut i8))) + + ;; NO_TNH: (type $arr-i32 (array (mut i32))) + + ;; NO_TNH: (type $arr-f32 (array (mut f32))) + + ;; NO_TNH: (type $arr-i16 (array (mut i16))) + + ;; NO_TNH: (type $arr-i64 (array (mut i64))) + + ;; NO_TNH: (type $arr-f64 (array (mut f64))) + + ;; NO_TNH: (type $arr-v128 (array (mut v128))) + + ;; NO_TNH: (import "a" "b" (func $get-i32 (type $8) (result i32))) + (import "a" "b" (func $get-i32 (result i32))) + + (type $arr-i8 (array (mut i8))) + (type $arr-i16 (array (mut i16))) + (type $arr-i32 (array (mut i32))) + (type $arr-i64 (array (mut i64))) + (type $arr-f32 (array (mut f32))) + (type $arr-f64 (array (mut f64))) + (type $arr-v128 (array (mut v128))) + + ;; TNH: (memory $mem 1) + ;; NO_TNH: (memory $mem 1) + (memory $mem 1) + ;; TNH: (data $active (i32.const 0) "abcd") + ;; NO_TNH: (data $active (i32.const 0) "abcd") + (data $active (i32.const 0) "abcd") + ;; TNH: (data $zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + ;; NO_TNH: (data $zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + ;; TNH: (data $uniform "****\01\02\01\02\01\02\01\02") + ;; NO_TNH: (data $uniform "****\01\02\01\02\01\02\01\02") + (data $uniform "\2a\2a\2a\2a\01\02\01\02\01\02\01\02") + ;; TNH: (data $mixed "\01\ff\03\04\05\06\07\08\00\00\80?\00\00\00\00\00\00\f0?") + ;; NO_TNH: (data $mixed "\01\ff\03\04\05\06\07\08\00\00\80?\00\00\00\00\00\00\f0?") + (data $mixed "\01\ff\03\04\05\06\07\08\00\00\80\3f\00\00\00\00\00\00\f0\3f") + ;; TNH: (data $large "\01\02\03\04\05\06\07\08\t\n\0b\0c\r\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !\"#$%&\'()*+,-./0123456789:;<=>?@A") + ;; NO_TNH: (data $large "\01\02\03\04\05\06\07\08\t\n\0b\0c\r\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !\"#$%&\'()*+,-./0123456789:;<=>?@A") + (data $large "\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f\20\21\22\23\24\25\26\27\28\29\2a\2b\2c\2d\2e\2f\30\31\32\33\34\35\36\37\38\39\3a\3b\3c\3d\3e\3f\40\41") + ;; TNH: (data $large-zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + ;; NO_TNH: (data $large-zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $large-zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + ;; TNH: (data $large-uniform "\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07") + ;; NO_TNH: (data $large-uniform "\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07") + (data $large-uniform "\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07\07") + ;; TNH: (data $floats "\00\00\00\80\00\00\00\80\00\00\c0\7f\00\00\c0\7f") + ;; NO_TNH: (data $floats "\00\00\00\80\00\00\00\80\00\00\c0\7f\00\00\c0\7f") + (data $floats "\00\00\00\80\00\00\00\80\00\00\c0\7f\00\00\c0\7f") + + ;; TNH: (func $array-new-data-empty (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-i8 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-i8 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-empty (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_fixed $arr-i8 0) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-empty + ;; Offset 0 and size 0 never traps (even if the segment was dropped), so it + ;; can be lowered to array.new_fixed 0 even without trapsNeverHappen. + (drop + (array.new_data $arr-i8 $mixed + (i32.const 0) + (i32.const 0) + ) + ) + ;; Non-zero in-bounds offset with size 0 traps if the segment was dropped at + ;; runtime, so it is only lowered with trapsNeverHappen. + (drop + (array.new_data $arr-i8 $mixed + (i32.const 4) + (i32.const 0) + ) + ) + ) + + ;; TNH: (func $array-new-data-zeros (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-i8 + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-i8 + ;; TNH-NEXT: (i32.const 5) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-i16 + ;; TNH-NEXT: (i32.const 4) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-i32 + ;; TNH-NEXT: (i32.const 3) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-i64 + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-f32 + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-f64 + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-v128 + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-zeros (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $zeros + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $zeros + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: (i32.const 5) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i16 $zeros + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i32 $zeros + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: (i32.const 3) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i64 $zeros + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-f32 $zeros + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-f64 $zeros + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-v128 $zeros + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-zeros + ;; All-zero slices become array.new_default in TNH mode. + (drop + (array.new_data $arr-i8 $zeros + (i32.const 0) + (i32.const 1) + ) + ) + (drop + (array.new_data $arr-i8 $zeros + (i32.const 2) + (i32.const 5) + ) + ) + (drop + (array.new_data $arr-i16 $zeros + (i32.const 0) + (i32.const 4) + ) + ) + (drop + (array.new_data $arr-i32 $zeros + (i32.const 4) + (i32.const 3) + ) + ) + (drop + (array.new_data $arr-i64 $zeros + (i32.const 0) + (i32.const 2) + ) + ) + (drop + (array.new_data $arr-f32 $zeros + (i32.const 0) + (i32.const 2) + ) + ) + (drop + (array.new_data $arr-f64 $zeros + (i32.const 0) + (i32.const 2) + ) + ) + (drop + (array.new_data $arr-v128 $zeros + (i32.const 0) + (i32.const 1) + ) + ) + ) + + ;; TNH: (func $array-new-data-uniform (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-i8 1 + ;; TNH-NEXT: (i32.const 42) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new $arr-i8 + ;; TNH-NEXT: (i32.const 42) + ;; TNH-NEXT: (i32.const 4) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new $arr-i16 + ;; TNH-NEXT: (i32.const 513) + ;; TNH-NEXT: (i32.const 4) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new $arr-i32 + ;; TNH-NEXT: (i32.const 33620481) + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-uniform (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $uniform + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $uniform + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i16 $uniform + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i32 $uniform + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-uniform + ;; Size 1 non-zero becomes array.new_fixed of size 1 (more compact than + ;; array.new). + (drop + (array.new_data $arr-i8 $uniform + (i32.const 0) + (i32.const 1) + ) + ) + ;; Size >= 2 with identical non-zero elements becomes array.new. + (drop + (array.new_data $arr-i8 $uniform + (i32.const 0) + (i32.const 4) + ) + ) + (drop + (array.new_data $arr-i16 $uniform + (i32.const 4) + (i32.const 4) + ) + ) + (drop + (array.new_data $arr-i32 $uniform + (i32.const 4) + (i32.const 2) + ) + ) + ) + + ;; TNH: (func $array-new-data-fixed (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-i8 4 + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: (i32.const -1) + ;; TNH-NEXT: (i32.const 3) + ;; TNH-NEXT: (i32.const 4) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-i16 2 + ;; TNH-NEXT: (i32.const -255) + ;; TNH-NEXT: (i32.const 1027) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-i32 2 + ;; TNH-NEXT: (i32.const 67370753) + ;; TNH-NEXT: (i32.const 134678021) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-i64 1 + ;; TNH-NEXT: (i64.const 578437695752371969) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-f32 1 + ;; TNH-NEXT: (f32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-f64 1 + ;; TNH-NEXT: (f64.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_fixed $arr-v128 1 + ;; TNH-NEXT: (v128.const i32x4 0x0403ff01 0x08070605 0x3f800000 0x00000000) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-fixed (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 4) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i16 $mixed + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i32 $mixed + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i64 $mixed + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-f32 $mixed + ;; NO_TNH-NEXT: (i32.const 8) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-f64 $mixed + ;; NO_TNH-NEXT: (i32.const 12) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-v128 $mixed + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-fixed + ;; Small non-uniform slices (size <= 64) become array.new_fixed. + (drop + (array.new_data $arr-i8 $mixed + (i32.const 0) + (i32.const 4) + ) + ) + (drop + (array.new_data $arr-i16 $mixed + (i32.const 0) + (i32.const 2) + ) + ) + (drop + (array.new_data $arr-i32 $mixed + (i32.const 0) + (i32.const 2) + ) + ) + (drop + (array.new_data $arr-i64 $mixed + (i32.const 0) + (i32.const 1) + ) + ) + (drop + (array.new_data $arr-f32 $mixed + (i32.const 8) + (i32.const 1) + ) + ) + (drop + (array.new_data $arr-f64 $mixed + (i32.const 12) + (i32.const 1) + ) + ) + (drop + (array.new_data $arr-v128 $mixed + (i32.const 0) + (i32.const 1) + ) + ) + ) + + ;; TNH: (func $array-new-data-negative (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i8 $large + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: (i32.const 65) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; TNH-NEXT: (i32.const 21) + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i32 $mixed + ;; TNH-NEXT: (i32.const 18) + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: (i32.const -1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i8 $active + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; TNH-NEXT: (call $get-i32) + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: (call $get-i32) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-negative (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $large + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 65) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; NO_TNH-NEXT: (i32.const 21) + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i32 $mixed + ;; NO_TNH-NEXT: (i32.const 18) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const -1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $active + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; NO_TNH-NEXT: (call $get-i32) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (call $get-i32) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-negative + ;; Size > 64 with non-uniform elements is not lowered. + (drop + (array.new_data $arr-i8 $large + (i32.const 0) + (i32.const 65) + ) + ) + ;; Out-of-bounds offset or size is not lowered. + (drop + (array.new_data $arr-i8 $mixed + (i32.const 21) + (i32.const 0) + ) + ) + (drop + (array.new_data $arr-i32 $mixed + (i32.const 18) + (i32.const 1) + ) + ) + ;; Negative (i.e. huge unsigned) size is out of bounds and not lowered. + (drop + (array.new_data $arr-i8 $mixed + (i32.const 0) + (i32.const -1) + ) + ) + ;; Active segment is not lowered. + (drop + (array.new_data $arr-i8 $active + (i32.const 0) + (i32.const 1) + ) + ) + ;; Non-constant offset or size is not lowered. + (drop + (array.new_data $arr-i8 $mixed + (call $get-i32) + (i32.const 1) + ) + ) + (drop + (array.new_data $arr-i8 $mixed + (i32.const 0) + (call $get-i32) + ) + ) + ) + + ;; TNH: (func $array-new-data-large-uniform (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_default $arr-i8 + ;; TNH-NEXT: (i32.const 72) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new $arr-i8 + ;; TNH-NEXT: (i32.const 7) + ;; TNH-NEXT: (i32.const 72) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-large-uniform (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $large-zeros + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 72) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $large-uniform + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 72) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-large-uniform + ;; Uniform slices are lowered regardless of size, even above the + ;; array.new_fixed limit. + (drop + (array.new_data $arr-i8 $large-zeros + (i32.const 0) + (i32.const 72) + ) + ) + (drop + (array.new_data $arr-i8 $large-uniform + (i32.const 0) + (i32.const 72) + ) + ) + ) + + ;; TNH: (func $array-new-data-floats (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new $arr-f32 + ;; TNH-NEXT: (f32.const -0) + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new $arr-f32 + ;; TNH-NEXT: (f32.const nan:0x400000) + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-floats (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-f32 $floats + ;; NO_TNH-NEXT: (i32.const 0) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-f32 $floats + ;; NO_TNH-NEXT: (i32.const 8) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-floats + ;; -0.0 is not the default value, so this becomes array.new, not + ;; array.new_default. + (drop + (array.new_data $arr-f32 $floats + (i32.const 0) + (i32.const 2) + ) + ) + ;; NaNs with identical bits are uniform. + (drop + (array.new_data $arr-f32 $floats + (i32.const 8) + (i32.const 2) + ) + ) + ) + + ;; TNH: (func $array-new-data-overflow (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; TNH-NEXT: (i32.const -1) + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-overflow (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (array.new_data $arr-i8 $mixed + ;; NO_TNH-NEXT: (i32.const -1) + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-overflow + ;; offset + size overflows 32 bits and is out of bounds; not lowered. + (drop + (array.new_data $arr-i8 $mixed + (i32.const -1) + (i32.const 2) + ) + ) + ) + + ;; TNH: (func $array-new-data-unreachable (type $1) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (block ;; (replaces unreachable ArrayNewData we can't emit) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (unreachable) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (unreachable) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $array-new-data-unreachable (type $1) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block ;; (replaces unreachable ArrayNewData we can't emit) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $array-new-data-unreachable + ;; Unreachable code is not lowered. + (drop + (array.new_data $arr-i8 $mixed + (unreachable) + (i32.const 1) + ) + ) + ) +) From e76189f804142313dd7426cf5da8ee93c95af7a3 Mon Sep 17 00:00:00 2001 From: Goktug Gokdogan Date: Tue, 13 Oct 2026 01:51:36 -0700 Subject: [PATCH 2/5] Address review feedback --- src/passes/OptimizeInstructions.cpp | 9 +++++---- src/wasm-interpreter.h | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 193128e92b3..025da873aba 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2365,6 +2365,7 @@ struct OptimizeInstructions auto* seg = getModule()->getDataSegment(curr->segment); if (!seg->isPassive()) { + // Active segments are implicitly dropped upon module instantiation. return; } @@ -2374,11 +2375,11 @@ struct OptimizeInstructions return; } - uint64_t offset = offsetConst->value.getUnsigned(); - uint64_t size = sizeConst->value.getUnsigned(); - uint64_t elemBytes = element.getByteSize(); + Address offset = offsetConst->value.getUnsigned(); + Address size = sizeConst->value.getUnsigned(); + Address elemBytes = element.getByteSize(); // offset and size are u32 and elemBytes <= 16, so this can't overflow. - uint64_t end = offset + size * elemBytes; + Address end = offset + size * elemBytes; if (end > seg->data.size()) { // This will trap. return; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index ceb3a794909..d5e358a081d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4747,9 +4747,11 @@ class ModuleRunnerBase : public ExpressionRunner { const auto& seg = *wasm.getDataSegment(curr->segment); auto elemBytes = element.getByteSize(); - // offset and size are u32 and elemBytes <= 16, so this can't overflow. - uint64_t end = offset + size * elemBytes; - if (end > seg.data.size()) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + + uint64_t end; + if (std::ckd_add(&end, offset, size * elemBytes) || end > seg.data.size()) { trap("out of bounds segment access in array.new_data"); } if (droppedDataSegments.contains(curr->segment) && end > 0) { @@ -4760,6 +4762,8 @@ class ModuleRunnerBase : public ExpressionRunner { memcpy(contents.data(), &seg.data[offset], size * elemBytes); } +#pragma GCC diagnostic pop + return self()->makeGCData(std::move(contents), curr->type); } Flow visitArrayNewElem(ArrayNewElem* curr) { From 48a748a0fd0f671bb48824426b171eb968816abe Mon Sep 17 00:00:00 2001 From: Goktug Gokdogan Date: Wed, 14 Oct 2026 00:17:19 -0700 Subject: [PATCH 3/5] Address review feedback: drop isNumber check, use ckd_add, add makeFromMemory(bytes), defer default to visitArrayNew --- src/literal.h | 4 ++ src/passes/OptimizeInstructions.cpp | 34 ++++------ src/wasm/literal.cpp | 38 ++++++----- .../optimize-instructions-array-new-data.wast | 63 ++++++++++++++----- 4 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src/literal.h b/src/literal.h index 335e117c147..dc568919191 100644 --- a/src/literal.h +++ b/src/literal.h @@ -214,6 +214,10 @@ class Literal { } static Literal makeFromMemory(const void* p, Type type); + // Reads an integer of the given byte size (1, 2, 4, 8 or 16) from memory. + // Sizes 1 and 2 are sign-extended to an i32, so this can also be used for + // packed fields. + static Literal makeFromMemory(const void* p, uint8_t bytes); static Literal makeSignedMin(Type type) { switch (type.getBasic()) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 025da873aba..a8edbc22103 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2371,16 +2371,13 @@ struct OptimizeInstructions auto heapType = curr->type.getHeapType(); const auto& element = heapType.getArray().element; - if (!element.type.isNumber()) { - return; - } Address offset = offsetConst->value.getUnsigned(); Address size = sizeConst->value.getUnsigned(); Address elemBytes = element.getByteSize(); - // offset and size are u32 and elemBytes <= 16, so this can't overflow. - Address end = offset + size * elemBytes; - if (end > seg->data.size()) { + Address end; + if (std::ckd_add(&end.addr, offset, size * elemBytes) || + end > seg->data.size()) { // This will trap. return; } @@ -2401,20 +2398,15 @@ struct OptimizeInstructions auto readElement = [&](Index i) -> Literal { const auto* src = &seg->data[offset + i * elemBytes]; - switch (element.packedType) { - case Field::NotPacked: - return Literal::makeFromMemory(src, element.type); + if (element.isPacked()) { // Packed values are truncated when stored, so the extension doesn't // matter; sign-extending gives the shorter encoding (-1 vs 255). - case Field::i8: - return Literal(int32_t(int8_t(*src))); - case Field::i16: - return Literal(int32_t(Bits::readLE(src))); + return Literal::makeFromMemory(src, uint8_t(elemBytes)); } - WASM_UNREACHABLE("unexpected packed type"); + return Literal::makeFromMemory(src, element.type); }; - // If all elements are equal, array.new(_default) is compact at any size. + // If all elements are equal, array.new is compact at any size. auto first = readElement(0); bool allEqual = true; for (Index i = 1; i < size; i++) { @@ -2424,15 +2416,11 @@ struct OptimizeInstructions } } if (allEqual) { + // visitArrayNew optimizes this further when the value is the default + // (to array.new_default) or the size is 1 (to array.new_fixed). auto* sizeExpr = builder.makeConst(int32_t(size)); - // Literal comparison is bitwise, so e.g. -0.0 is not considered zero. - if (first == Literal::makeZero(element.type)) { - replaceCurrent(builder.makeArrayNew(heapType, sizeExpr)); - } else { - // visitArrayNew turns the size == 1 case into array.new_fixed. - replaceCurrent( - builder.makeArrayNew(heapType, sizeExpr, builder.makeConst(first))); - } + replaceCurrent( + builder.makeArrayNew(heapType, sizeExpr, builder.makeConst(first))); return; } diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 272cc5a6404..df1f50c9fcd 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -316,26 +316,34 @@ Literal Literal::makeNegOne(Type type) { Literal Literal::makeFromMemory(const void* p, Type type) { assert(type.isNumber()); + auto ret = makeFromMemory(p, uint8_t(type.getByteSize())); switch (type.getBasic()) { - case Type::i32: { + case Type::f32: + return ret.castToF32(); + case Type::f64: + return ret.castToF64(); + default: + return ret; + } +} + +Literal Literal::makeFromMemory(const void* p, uint8_t bytes) { + switch (bytes) { + case 1: + return Literal(int32_t(Bits::readLE(p))); + case 2: + return Literal(int32_t(Bits::readLE(p))); + case 4: return Literal(Bits::readLE(p)); - } - case Type::i64: { + case 8: return Literal(Bits::readLE(p)); - } - case Type::f32: { - return Literal(bit_cast(Bits::readLE(p))); - } - case Type::f64: { - return Literal(bit_cast(Bits::readLE(p))); - } - case Type::v128: { - uint8_t bytes[16]; - memcpy(bytes, p, sizeof(bytes)); - return Literal(bytes); + case 16: { + uint8_t buf[16]; + memcpy(buf, p, sizeof(buf)); + return Literal(buf); } default: - WASM_UNREACHABLE("unexpected type"); + WASM_UNREACHABLE("unexpected size"); } } diff --git a/test/lit/passes/optimize-instructions-array-new-data.wast b/test/lit/passes/optimize-instructions-array-new-data.wast index 8f601d813cd..be31f56a5a6 100644 --- a/test/lit/passes/optimize-instructions-array-new-data.wast +++ b/test/lit/passes/optimize-instructions-array-new-data.wast @@ -116,33 +116,63 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (array.new_default $arr-i8 - ;; TNH-NEXT: (i32.const 5) + ;; TNH-NEXT: (block (result (ref (exact $arr-i8))) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (array.new_default $arr-i8 + ;; TNH-NEXT: (i32.const 5) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (array.new_default $arr-i16 - ;; TNH-NEXT: (i32.const 4) + ;; TNH-NEXT: (block (result (ref (exact $arr-i16))) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (array.new_default $arr-i16 + ;; TNH-NEXT: (i32.const 4) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (array.new_default $arr-i32 - ;; TNH-NEXT: (i32.const 3) + ;; TNH-NEXT: (block (result (ref (exact $arr-i32))) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (array.new_default $arr-i32 + ;; TNH-NEXT: (i32.const 3) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (array.new_default $arr-i64 - ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: (block (result (ref (exact $arr-i64))) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (i64.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (array.new_default $arr-i64 + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (array.new_default $arr-f32 - ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: (block (result (ref (exact $arr-f32))) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (f32.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (array.new_default $arr-f32 + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (array.new_default $arr-f64 - ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: (block (result (ref (exact $arr-f64))) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (f64.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (array.new_default $arr-f64 + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop @@ -606,8 +636,13 @@ ;; TNH: (func $array-new-data-large-uniform (type $1) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (array.new_default $arr-i8 - ;; TNH-NEXT: (i32.const 72) + ;; TNH-NEXT: (block (result (ref (exact $arr-i8))) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (i32.const 0) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (array.new_default $arr-i8 + ;; TNH-NEXT: (i32.const 72) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop From d702d51dfe91767b61f22a7601958f7f68db7773 Mon Sep 17 00:00:00 2001 From: Goktug Gokdogan Date: Wed, 14 Oct 2026 15:49:35 -0700 Subject: [PATCH 4/5] Use an optional byteSize param on makeFromMemory; clarify uniform test data --- src/literal.h | 10 ++-- src/passes/OptimizeInstructions.cpp | 9 ++-- src/wasm/literal.cpp | 54 ++++++++++--------- .../optimize-instructions-array-new-data.wast | 3 ++ 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/literal.h b/src/literal.h index dc568919191..791ce5f0505 100644 --- a/src/literal.h +++ b/src/literal.h @@ -213,11 +213,11 @@ class Literal { } } - static Literal makeFromMemory(const void* p, Type type); - // Reads an integer of the given byte size (1, 2, 4, 8 or 16) from memory. - // Sizes 1 and 2 are sign-extended to an i32, so this can also be used for - // packed fields. - static Literal makeFromMemory(const void* p, uint8_t bytes); + // Reads a value of the given type from memory. For i32, a smaller byteSize + // (1 or 2) can be given to read a sign-extended packed value. A byteSize of + // 0 means the full size of the type. + static Literal + makeFromMemory(const void* p, Type type, unsigned byteSize = 0); static Literal makeSignedMin(Type type) { switch (type.getBasic()) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index a8edbc22103..49457b8b113 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2398,12 +2398,9 @@ struct OptimizeInstructions auto readElement = [&](Index i) -> Literal { const auto* src = &seg->data[offset + i * elemBytes]; - if (element.isPacked()) { - // Packed values are truncated when stored, so the extension doesn't - // matter; sign-extending gives the shorter encoding (-1 vs 255). - return Literal::makeFromMemory(src, uint8_t(elemBytes)); - } - return Literal::makeFromMemory(src, element.type); + // Packed values are truncated when stored, so the extension doesn't + // matter; sign-extending gives the shorter encoding (-1 vs 255). + return Literal::makeFromMemory(src, element.type, element.getByteSize()); }; // If all elements are equal, array.new is compact at any size. diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index df1f50c9fcd..02db45b8e77 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -314,36 +314,40 @@ Literal Literal::makeNegOne(Type type) { return makeFromInt32(-1, type); } -Literal Literal::makeFromMemory(const void* p, Type type) { +Literal Literal::makeFromMemory(const void* p, Type type, unsigned byteSize) { assert(type.isNumber()); - auto ret = makeFromMemory(p, uint8_t(type.getByteSize())); - switch (type.getBasic()) { - case Type::f32: - return ret.castToF32(); - case Type::f64: - return ret.castToF64(); - default: - return ret; + if (byteSize == 0) { + byteSize = type.getByteSize(); } -} - -Literal Literal::makeFromMemory(const void* p, uint8_t bytes) { - switch (bytes) { - case 1: - return Literal(int32_t(Bits::readLE(p))); - case 2: - return Literal(int32_t(Bits::readLE(p))); - case 4: - return Literal(Bits::readLE(p)); - case 8: + assert(byteSize == type.getByteSize() || + (type == Type::i32 && (byteSize == 1 || byteSize == 2))); + switch (type.getBasic()) { + case Type::i32: { + switch (byteSize) { + case 1: + return Literal(int32_t(Bits::readLE(p))); + case 2: + return Literal(int32_t(Bits::readLE(p))); + default: + return Literal(Bits::readLE(p)); + } + } + case Type::i64: { return Literal(Bits::readLE(p)); - case 16: { - uint8_t buf[16]; - memcpy(buf, p, sizeof(buf)); - return Literal(buf); + } + case Type::f32: { + return Literal(bit_cast(Bits::readLE(p))); + } + case Type::f64: { + return Literal(bit_cast(Bits::readLE(p))); + } + case Type::v128: { + uint8_t bytes[16]; + memcpy(bytes, p, sizeof(bytes)); + return Literal(bytes); } default: - WASM_UNREACHABLE("unexpected size"); + WASM_UNREACHABLE("unexpected type"); } } diff --git a/test/lit/passes/optimize-instructions-array-new-data.wast b/test/lit/passes/optimize-instructions-array-new-data.wast index be31f56a5a6..cafcbdc819b 100644 --- a/test/lit/passes/optimize-instructions-array-new-data.wast +++ b/test/lit/passes/optimize-instructions-array-new-data.wast @@ -52,6 +52,9 @@ ;; TNH: (data $zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") ;; NO_TNH: (data $zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") (data $zeros "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + ;; Uniform means all elements are equal when read at the element width. The + ;; first 4 bytes are identical i8s; the rest is not uniform as i8, but is as + ;; i16 (0x0201) and as i32 (0x02010201). ;; TNH: (data $uniform "****\01\02\01\02\01\02\01\02") ;; NO_TNH: (data $uniform "****\01\02\01\02\01\02\01\02") (data $uniform "\2a\2a\2a\2a\01\02\01\02\01\02\01\02") From c15f70dfd1eba57e8363022e1f76b760e02c1721 Mon Sep 17 00:00:00 2001 From: Goktug Gokdogan Date: Wed, 14 Oct 2026 17:12:14 -0700 Subject: [PATCH 5/5] Use std::optional for makeFromMemory byteSize --- src/literal.h | 9 +++++---- src/wasm/literal.cpp | 14 +++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/literal.h b/src/literal.h index 791ce5f0505..07352a6eab8 100644 --- a/src/literal.h +++ b/src/literal.h @@ -19,6 +19,7 @@ #include #include +#include #include #include "support/bits.h" @@ -214,10 +215,10 @@ class Literal { } // Reads a value of the given type from memory. For i32, a smaller byteSize - // (1 or 2) can be given to read a sign-extended packed value. A byteSize of - // 0 means the full size of the type. - static Literal - makeFromMemory(const void* p, Type type, unsigned byteSize = 0); + // (1 or 2) can be given to read a sign-extended packed value. By default the + // full size of the type is read. + static Literal makeFromMemory( + const void* p, Type type, std::optional byteSize = std::nullopt); static Literal makeSignedMin(Type type) { switch (type.getBasic()) { diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 02db45b8e77..5c15e502198 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -314,16 +314,16 @@ Literal Literal::makeNegOne(Type type) { return makeFromInt32(-1, type); } -Literal Literal::makeFromMemory(const void* p, Type type, unsigned byteSize) { +Literal Literal::makeFromMemory(const void* p, + Type type, + std::optional byteSize) { assert(type.isNumber()); - if (byteSize == 0) { - byteSize = type.getByteSize(); - } - assert(byteSize == type.getByteSize() || - (type == Type::i32 && (byteSize == 1 || byteSize == 2))); + auto size = byteSize.value_or(type.getByteSize()); + assert(size == type.getByteSize() || + (type == Type::i32 && (size == 1 || size == 2))); switch (type.getBasic()) { case Type::i32: { - switch (byteSize) { + switch (size) { case 1: return Literal(int32_t(Bits::readLE(p))); case 2: