Skip to content

Commit 24ddcf4

Browse files
kripkenradekdoulik
authored andcommitted
StringLowering: Start to lower instructions (WebAssembly#6281)
1 parent f6d02ca commit 24ddcf4

File tree

4 files changed

+198
-0
lines changed

4 files changed

+198
-0
lines changed

scripts/fuzz_opt.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ def is_git_repo():
286286
# TODO: fuzzer and interpreter support for strings
287287
'strings.wast',
288288
'simplify-locals-strings.wast',
289+
'string-lowering-instructions.wast',
289290
# TODO: fuzzer and interpreter support for extern conversions
290291
'extern-conversions.wast',
291292
# ignore DWARF because it is incompatible with multivalue atm

src/passes/StringLowering.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ struct StringLowering : public StringGathering {
191191
// Remove all HeapType::string etc. in favor of externref.
192192
updateTypes(module);
193193

194+
// Replace string.* etc. operations with imported ones.
195+
replaceInstructions(module);
196+
194197
// Disable the feature here after we lowered everything away.
195198
module->features.disable(FeatureSet::Strings);
196199
}
@@ -225,9 +228,88 @@ struct StringLowering : public StringGathering {
225228

226229
void updateTypes(Module* module) {
227230
TypeMapper::TypeUpdates updates;
231+
// There is no difference between strings and views with imported strings:
232+
// they are all just JS strings, so they all turn into externref.
228233
updates[HeapType::string] = HeapType::ext;
234+
updates[HeapType::stringview_wtf8] = HeapType::ext;
235+
updates[HeapType::stringview_wtf16] = HeapType::ext;
236+
updates[HeapType::stringview_iter] = HeapType::ext;
229237
TypeMapper(*module, updates).map();
230238
}
239+
240+
// Imported string functions.
241+
Name fromCharCodeArrayImport;
242+
Name fromCodePointImport;
243+
244+
// The name of the module to import string functions from.
245+
Name WasmStringsModule = "wasm:js-string";
246+
247+
// Common types used in imports.
248+
Type nullArray16 = Type(Array(Field(Field::i16, Mutable)), Nullable);
249+
Type nnExt = Type(HeapType::ext, NonNullable);
250+
251+
// Creates an imported string function, returning its name (which is equal to
252+
// the true name of the import, if there is no conflict).
253+
Name addImport(Module* module, Name trueName, Type params, Type results) {
254+
auto name = Names::getValidFunctionName(*module, trueName);
255+
auto sig = Signature(params, results);
256+
Builder builder(*module);
257+
auto* func = module->addFunction(builder.makeFunction(name, sig, {}));
258+
func->module = WasmStringsModule;
259+
func->base = trueName;
260+
return name;
261+
}
262+
263+
void replaceInstructions(Module* module) {
264+
// Add all the possible imports up front, to avoid adding them during
265+
// parallel work. Optimizations can remove unneeded ones later.
266+
267+
// string.fromCharCodeArray: array, start, end -> ext
268+
fromCharCodeArrayImport = addImport(
269+
module, "fromCharCodeArray", {nullArray16, Type::i32, Type::i32}, nnExt);
270+
// string.fromCodePoint: codepoint -> ext
271+
fromCodePointImport = addImport(module, "fromCodePoint", Type::i32, nnExt);
272+
273+
// Replace the string instructions in parallel.
274+
struct Replacer : public WalkerPass<PostWalker<Replacer>> {
275+
bool isFunctionParallel() override { return true; }
276+
277+
StringLowering& lowering;
278+
279+
std::unique_ptr<Pass> create() override {
280+
return std::make_unique<Replacer>(lowering);
281+
}
282+
283+
Replacer(StringLowering& lowering) : lowering(lowering) {}
284+
285+
void visitStringNew(StringNew* curr) {
286+
Builder builder(*getModule());
287+
switch (curr->op) {
288+
case StringNewWTF16Array:
289+
replaceCurrent(builder.makeCall(lowering.fromCharCodeArrayImport,
290+
{curr->ptr, curr->start, curr->end},
291+
lowering.nnExt));
292+
return;
293+
case StringNewFromCodePoint:
294+
replaceCurrent(builder.makeCall(
295+
lowering.fromCodePointImport, {curr->ptr}, lowering.nnExt));
296+
return;
297+
default:
298+
WASM_UNREACHABLE("TODO: all of string.new*");
299+
}
300+
}
301+
302+
void visitStringAs(StringAs* curr) {
303+
// There is no difference between strings and views with imported
304+
// strings: they are all just JS strings, so no conversion is needed.
305+
replaceCurrent(curr->ref);
306+
}
307+
};
308+
309+
Replacer replacer(*this);
310+
replacer.run(getPassRunner(), module);
311+
replacer.walkModuleCode(module);
312+
}
231313
};
232314

233315
Pass* createStringGatheringPass() { return new StringGathering(); }

test/lit/passes/string-gathering.wast

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,22 @@
2727
;; CHECK: (global $global2 stringref (global.get $string.const_bar))
2828
;; LOWER: (type $0 (func))
2929

30+
;; LOWER: (type $1 (array (mut i16)))
31+
32+
;; LOWER: (type $2 (func (param (ref null $1) i32 i32) (result (ref extern))))
33+
34+
;; LOWER: (type $3 (func (param i32) (result (ref extern))))
35+
3036
;; LOWER: (import "string.const" "0" (global $string.const_bar (ref extern)))
3137

3238
;; LOWER: (import "string.const" "1" (global $string.const_other (ref extern)))
3339

3440
;; LOWER: (import "string.const" "2" (global $global (ref extern)))
3541

42+
;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $2) (param (ref null $1) i32 i32) (result (ref extern))))
43+
44+
;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $3) (param i32) (result (ref extern))))
45+
3646
;; LOWER: (global $global2 externref (global.get $string.const_bar))
3747
(global $global2 (ref null string) (string.const "bar"))
3848

@@ -111,6 +121,12 @@
111121
;; Multiple possible reusable globals. Also test ignoring of imports.
112122
(module
113123
;; CHECK: (import "a" "b" (global $import (ref string)))
124+
;; LOWER: (type $0 (array (mut i16)))
125+
126+
;; LOWER: (type $1 (func (param (ref null $0) i32 i32) (result (ref extern))))
127+
128+
;; LOWER: (type $2 (func (param i32) (result (ref extern))))
129+
114130
;; LOWER: (import "a" "b" (global $import (ref extern)))
115131
(import "a" "b" (global $import (ref string)))
116132

@@ -122,6 +138,10 @@
122138

123139
;; LOWER: (import "string.const" "1" (global $global4 (ref extern)))
124140

141+
;; LOWER: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $1) (param (ref null $0) i32 i32) (result (ref extern))))
142+
143+
;; LOWER: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint (type $2) (param i32) (result (ref extern))))
144+
125145
;; LOWER: (global $global2 (ref extern) (global.get $global1))
126146
(global $global2 (ref string) (string.const "foo"))
127147

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: foreach %s %t wasm-opt --string-lowering -all -S -o - | filecheck %s
4+
5+
(module
6+
;; CHECK: (type $0 (func))
7+
8+
;; CHECK: (type $array16 (array (mut i16)))
9+
(type $array16 (array (mut i16)))
10+
11+
;; CHECK: (rec
12+
;; CHECK-NEXT: (type $2 (func (param (ref $array16))))
13+
14+
;; CHECK: (type $3 (func (param externref externref externref externref)))
15+
16+
;; CHECK: (type $4 (func (param (ref null $array16) i32 i32) (result (ref extern))))
17+
18+
;; CHECK: (type $5 (func (param i32) (result (ref extern))))
19+
20+
;; CHECK: (import "colliding" "name" (func $fromCodePoint (type $0)))
21+
(import "colliding" "name" (func $fromCodePoint))
22+
23+
;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $4) (param (ref null $array16) i32 i32) (result (ref extern))))
24+
25+
;; CHECK: (import "wasm:js-string" "fromCodePoint" (func $fromCodePoint_5 (type $5) (param i32) (result (ref extern))))
26+
27+
;; CHECK: (func $string.as (type $3) (param $a externref) (param $b externref) (param $c externref) (param $d externref)
28+
;; CHECK-NEXT: (local.set $b
29+
;; CHECK-NEXT: (local.get $a)
30+
;; CHECK-NEXT: )
31+
;; CHECK-NEXT: (local.set $c
32+
;; CHECK-NEXT: (local.get $a)
33+
;; CHECK-NEXT: )
34+
;; CHECK-NEXT: (local.set $d
35+
;; CHECK-NEXT: (local.get $a)
36+
;; CHECK-NEXT: )
37+
;; CHECK-NEXT: )
38+
(func $string.as
39+
(param $a stringref)
40+
(param $b stringview_wtf8)
41+
(param $c stringview_wtf16)
42+
(param $d stringview_iter)
43+
;; These operations all vanish in the lowering, as they all become extref
44+
;; (JS strings).
45+
(local.set $b
46+
(string.as_wtf8
47+
(local.get $a)
48+
)
49+
)
50+
(local.set $c
51+
(string.as_wtf16
52+
(local.get $a)
53+
)
54+
)
55+
(local.set $d
56+
(string.as_iter
57+
(local.get $a)
58+
)
59+
)
60+
)
61+
62+
;; CHECK: (func $string.new.gc (type $2) (param $array16 (ref $array16))
63+
;; CHECK-NEXT: (drop
64+
;; CHECK-NEXT: (call $fromCharCodeArray
65+
;; CHECK-NEXT: (local.get $array16)
66+
;; CHECK-NEXT: (i32.const 7)
67+
;; CHECK-NEXT: (i32.const 8)
68+
;; CHECK-NEXT: )
69+
;; CHECK-NEXT: )
70+
;; CHECK-NEXT: )
71+
(func $string.new.gc (param $array16 (ref $array16))
72+
(drop
73+
(string.new_wtf16_array
74+
(local.get $array16)
75+
(i32.const 7)
76+
(i32.const 8)
77+
)
78+
)
79+
)
80+
81+
;; CHECK: (func $string.from_code_point (type $0)
82+
;; CHECK-NEXT: (drop
83+
;; CHECK-NEXT: (call $fromCodePoint_5
84+
;; CHECK-NEXT: (i32.const 1)
85+
;; CHECK-NEXT: )
86+
;; CHECK-NEXT: )
87+
;; CHECK-NEXT: )
88+
(func $string.from_code_point
89+
(drop
90+
(string.from_code_point
91+
(i32.const 1)
92+
)
93+
)
94+
)
95+
)

0 commit comments

Comments
 (0)