Skip to content

Commit

Permalink
StringLowering: Use an array16 type in its own rec group (#6302)
Browse files Browse the repository at this point in the history
The input module might use an array of 16-bit elements type that is somewhere in a
giant rec group, but that is not valid for imported strings: that array type is now on an
import and must match the expected ABI, which is to be in its own personal rec group.

The old array16 type remains in the module after this transformation, but all uses of it
are replaced with uses of the new array16 type.

Also move makeImports to after updateTypes: there are no types to update in the new
imports. That does not matter but it can make debugging less pleasant, so improve it.
  • Loading branch information
kripken authored Feb 14, 2024
1 parent 78516ae commit 9784f01
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 52 deletions.
34 changes: 25 additions & 9 deletions src/passes/StringLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ struct StringLowering : public StringGathering {
// First, run the gathering operation so all string.consts are in one place.
StringGathering::run(module);

// Lower the string.const globals into imports.
makeImports(module);

// Remove all HeapType::string etc. in favor of externref.
updateTypes(module);

// Lower the string.const globals into imports.
makeImports(module);

// Replace string.* etc. operations with imported ones.
replaceInstructions(module);

Expand Down Expand Up @@ -230,6 +230,11 @@ struct StringLowering : public StringGathering {
CustomSection{"string.consts", std::move(vec)});
}

// Common types used in imports.
Type nullArray16 = Type(Array(Field(Field::i16, Mutable)), Nullable);
Type nullExt = Type(HeapType::ext, Nullable);
Type nnExt = Type(HeapType::ext, NonNullable);

void updateTypes(Module* module) {
TypeMapper::TypeUpdates updates;

Expand All @@ -240,8 +245,24 @@ struct StringLowering : public StringGathering {
updates[HeapType::stringview_wtf16] = HeapType::ext;
updates[HeapType::stringview_iter] = HeapType::ext;

// The module may have its own array16 type inside a big rec group, but
// imported strings expects that type in its own rec group as part of the
// ABI. Fix that up here. (This is valid to do as this type has no sub- or
// super-types anyhow; it is "plain old data" for communicating with the
// outside.)
auto allTypes = ModuleUtils::collectHeapTypes(*module);
auto array16 = nullArray16.getHeapType();
auto array16Element = array16.getArray().element;
for (auto type : allTypes) {
// Match an array type with no super and that is closed.
if (type.isArray() && !type.getDeclaredSuperType() && !type.isOpen() &&
type.getArray().element == array16Element) {
updates[type] = array16;
}
}

// We consider all types that use strings as modifiable, which means we
// mark them as non-private. That is, we are doing something TypeMapper
// mark them as non-public. That is, we are doing something TypeMapper
// normally does not, as we are changing the external interface/ABI of the
// module: we are changing that ABI from using strings to externs.
auto publicTypes = ModuleUtils::getPublicHeapTypes(*module);
Expand All @@ -268,11 +289,6 @@ struct StringLowering : public StringGathering {
// The name of the module to import string functions from.
Name WasmStringsModule = "wasm:js-string";

// Common types used in imports.
Type nullArray16 = Type(Array(Field(Field::i16, Mutable)), Nullable);
Type nullExt = Type(HeapType::ext, Nullable);
Type nnExt = Type(HeapType::ext, NonNullable);

// Creates an imported string function, returning its name (which is equal to
// the true name of the import, if there is no conflict).
Name addImport(Module* module, Name trueName, Type params, Type results) {
Expand Down
161 changes: 118 additions & 43 deletions test/lit/passes/string-lowering-instructions.wast
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,89 @@
;; RUN: foreach %s %t wasm-opt --string-lowering -all -S -o - | filecheck %s

(module
;; CHECK: (type $array16 (array (mut i16)))
(type $array16 (array (mut i16)))
(rec
;; CHECK: (type $0 (array (mut i16)))

;; CHECK: (type $1 (func (param externref externref) (result i32)))
;; CHECK: (type $1 (func))

;; CHECK: (rec
;; CHECK-NEXT: (type $2 (func (param externref i32 externref)))
;; CHECK: (type $2 (func (param externref externref) (result i32)))

;; CHECK: (type $3 (func (result externref)))
;; CHECK: (rec
;; CHECK-NEXT: (type $3 (func (param externref i32 externref)))

;; CHECK: (type $4 (func (param externref) (result externref)))
;; CHECK: (type $4 (func (result externref)))

;; CHECK: (type $5 (func (param externref) (result externref)))
;; CHECK: (type $struct-of-array (struct (field (ref $0))))
(type $struct-of-array (struct (field (ref $array16))))

;; CHECK: (type $6 (func (param externref) (result i32)))
;; CHECK: (type $array16-imm (array i32))
(type $array16-imm (array i32))

;; CHECK: (type $7 (func (param externref externref) (result i32)))
;; CHECK: (type $array32 (array (mut i32)))
(type $array32 (array (mut i32)))

;; CHECK: (type $8 (func (param externref (ref $array16)) (result i32)))
;; CHECK: (type $array16-open (sub (array (mut i16))))
(type $array16-open (sub (array (mut i16))))

;; CHECK: (type $9 (func (param (ref $array16))))
;; CHECK: (type $array16-child (sub $array16-open (array (mut i16))))
(type $array16-child (sub $array16-open (array (mut i16))))

;; CHECK: (type $10 (func (param externref externref externref externref)))
;; CHECK: (type $array16 (array (mut i16)))
(type $array16 (array (mut i16)))
)

;; CHECK: (type $11 (func (param externref) (result externref)))

;; CHECK: (type $12 (func (param externref) (result externref)))

;; CHECK: (type $13 (func (param externref) (result i32)))

;; CHECK: (type $14 (func (param externref externref) (result i32)))

;; CHECK: (type $15 (func (param externref (ref $0)) (result i32)))

;; CHECK: (type $11 (func))
;; CHECK: (type $16 (func (param (ref $0))))

;; CHECK: (type $12 (func (param (ref null $array16) i32 i32) (result (ref extern))))
;; CHECK: (type $17 (func (param externref externref externref externref)))

;; CHECK: (type $13 (func (param i32) (result (ref extern))))
;; CHECK: (type $18 (func (param (ref null $0) i32 i32) (result (ref extern))))

;; CHECK: (type $14 (func (param externref (ref null $array16) i32) (result i32)))
;; CHECK: (type $19 (func (param i32) (result (ref extern))))

;; CHECK: (type $15 (func (param externref) (result i32)))
;; CHECK: (type $20 (func (param externref (ref null $0) i32) (result i32)))

;; CHECK: (type $16 (func (param externref i32) (result i32)))
;; CHECK: (type $21 (func (param externref) (result i32)))

;; CHECK: (type $17 (func (param externref i32 i32) (result (ref extern))))
;; CHECK: (type $22 (func (param externref i32) (result i32)))

;; CHECK: (type $23 (func (param externref i32 i32) (result (ref extern))))

;; CHECK: (import "string.const" "0" (global $string.const_exported (ref extern)))

;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $11)))
;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $1)))
(import "colliding" "name" (func $fromCodePoint))

;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $12) (param (ref null $array16) i32 i32) (result (ref extern))))
;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $18) (param (ref null $0) i32 i32) (result (ref extern))))

;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_15 (type $13) (param i32) (result (ref extern))))
;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_16 (type $19) (param i32) (result (ref extern))))

;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $14) (param externref (ref null $array16) i32) (result i32)))
;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $20) (param externref (ref null $0) i32) (result i32)))

;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $1) (param externref externref) (result i32)))
;; CHECK: (import "wasm:js-string" "equals" (func $equals (type $2) (param externref externref) (result i32)))

;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $1) (param externref externref) (result i32)))
;; CHECK: (import "wasm:js-string" "compare" (func $compare (type $2) (param externref externref) (result i32)))

;; CHECK: (import "wasm:js-string" "length" (func $length (type $15) (param externref) (result i32)))
;; CHECK: (import "wasm:js-string" "length" (func $length (type $21) (param externref) (result i32)))

;; CHECK: (import "wasm:js-string" "codePointAt" (func $codePointAt (type $16) (param externref i32) (result i32)))
;; CHECK: (import "wasm:js-string" "codePointAt" (func $codePointAt (type $22) (param externref i32) (result i32)))

;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $17) (param externref i32 i32) (result (ref extern))))
;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $23) (param externref i32 i32) (result (ref extern))))

;; CHECK: (export "export.1" (func $exported-string-returner))

;; CHECK: (export "export.2" (func $exported-string-receiver))

;; CHECK: (func $string.as (type $10) (param $a externref) (param $b externref) (param $c externref) (param $d externref)
;; CHECK: (func $string.as (type $17) (param $a externref) (param $b externref) (param $c externref) (param $d externref)
;; CHECK-NEXT: (local.set $b
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
Expand Down Expand Up @@ -101,7 +120,7 @@
)
)

;; CHECK: (func $string.new.gc (type $9) (param $array16 (ref $array16))
;; CHECK: (func $string.new.gc (type $16) (param $array16 (ref $0))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $fromCharCodeArray
;; CHECK-NEXT: (local.get $array16)
Expand All @@ -120,8 +139,8 @@
)
)

;; CHECK: (func $string.from_code_point (type $3) (result externref)
;; CHECK-NEXT: (call $fromCodePoint_15
;; CHECK: (func $string.from_code_point (type $4) (result externref)
;; CHECK-NEXT: (call $fromCodePoint_16
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
Expand All @@ -131,7 +150,7 @@
)
)

;; CHECK: (func $string.encode (type $8) (param $ref externref) (param $array16 (ref $array16)) (result i32)
;; CHECK: (func $string.encode (type $15) (param $ref externref) (param $array16 (ref $0)) (result i32)
;; CHECK-NEXT: (call $intoCharCodeArray
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (local.get $array16)
Expand All @@ -146,7 +165,7 @@
)
)

;; CHECK: (func $string.eq (type $7) (param $a externref) (param $b externref) (result i32)
;; CHECK: (func $string.eq (type $14) (param $a externref) (param $b externref) (result i32)
;; CHECK-NEXT: (call $equals
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: (local.get $b)
Expand All @@ -159,7 +178,7 @@
)
)

;; CHECK: (func $string.compare (type $7) (param $a externref) (param $b externref) (result i32)
;; CHECK: (func $string.compare (type $14) (param $a externref) (param $b externref) (result i32)
;; CHECK-NEXT: (call $compare
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: (local.get $b)
Expand All @@ -172,7 +191,7 @@
)
)

;; CHECK: (func $string.length (type $6) (param $ref externref) (result i32)
;; CHECK: (func $string.length (type $13) (param $ref externref) (result i32)
;; CHECK-NEXT: (call $length
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
Expand All @@ -183,7 +202,7 @@
)
)

;; CHECK: (func $string.get_codeunit (type $6) (param $ref externref) (result i32)
;; CHECK: (func $string.get_codeunit (type $13) (param $ref externref) (result i32)
;; CHECK-NEXT: (call $codePointAt
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (i32.const 2)
Expand All @@ -196,7 +215,7 @@
)
)

;; CHECK: (func $string.slice (type $5) (param $ref externref) (result externref)
;; CHECK: (func $string.slice (type $12) (param $ref externref) (result externref)
;; CHECK-NEXT: (call $substring
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (i32.const 2)
Expand All @@ -211,7 +230,7 @@
)
)

;; CHECK: (func $if.string (type $4) (param $ref externref) (result externref)
;; CHECK: (func $if.string (type $11) (param $ref externref) (result externref)
;; CHECK-NEXT: (if (result externref)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (then
Expand All @@ -234,7 +253,7 @@
)
)

;; CHECK: (func $if.string.flip (type $4) (param $ref externref) (result externref)
;; CHECK: (func $if.string.flip (type $11) (param $ref externref) (result externref)
;; CHECK-NEXT: (if (result externref)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (then
Expand All @@ -258,7 +277,7 @@
)
)

;; CHECK: (func $exported-string-returner (type $3) (result externref)
;; CHECK: (func $exported-string-returner (type $4) (result externref)
;; CHECK-NEXT: (global.get $string.const_exported)
;; CHECK-NEXT: )
(func $exported-string-returner (export "export.1") (result stringref)
Expand All @@ -267,7 +286,7 @@
(string.const "exported")
)

;; CHECK: (func $exported-string-receiver (type $2) (param $x externref) (param $y i32) (param $z externref)
;; CHECK: (func $exported-string-receiver (type $3) (param $x externref) (param $y i32) (param $z externref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
Expand All @@ -291,4 +310,60 @@
(local.get $z)
)
)

;; CHECK: (func $use-struct-of-array (type $1)
;; CHECK-NEXT: (local $array16 (ref $0))
;; CHECK-NEXT: (local $open (ref $array16-open))
;; CHECK-NEXT: (local $child (ref $array16-child))
;; CHECK-NEXT: (local $32 (ref $array32))
;; CHECK-NEXT: (local $imm (ref $array16-imm))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $fromCharCodeArray
;; CHECK-NEXT: (struct.get $struct-of-array 0
;; CHECK-NEXT: (struct.new $struct-of-array
;; CHECK-NEXT: (array.new_fixed $0 2
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $use-struct-of-array
;; The array type here should switch to the new 16-bit array type that we
;; use for the new imports, so that it is compatible with them. Without
;; that, calling the import as we do below will fail.
(local $array16 (ref $array16))

;; In comparison, the array16-open param should remain as it is: it is an
;; open type which is different then the one we care about.
(local $open (ref $array16-open))

;; Likewise a child of that open type is also ignored.
(local $child (ref $array16-child))

;; Another array size is also ignored.
(local $32 (ref $array32))

;; An immutable array is also ignored.
(local $imm (ref $array16-imm))

(drop
(string.new_wtf16_array
(struct.get $struct-of-array 0
(struct.new $struct-of-array
(array.new_fixed $array16 2
(i32.const 10)
(i32.const 20)
)
)
)
(i32.const 0)
(i32.const 1)
)
)
)
)

0 comments on commit 9784f01

Please sign in to comment.