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
52 changes: 36 additions & 16 deletions src/passes/StringLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -423,24 +423,44 @@ struct StringLowering : public StringGathering {
}
}

// Additional hacks.
// Additional hacks: We fix up a none that should be noext. Before the
// lowering we can use none for stringref, but after we must use noext as
// the two do not share a bottom type.
//
// The code here and in the visitors below is course wildly insufficient
// (we need selects and blocks and all other joins, and not just nulls,
// etc.) but in practice this is enough for now. TODO extend as needed
void ensureNullIsExt(Expression* curr) {
if (auto* null = curr->dynCast<RefNull>()) {
null->finalize(HeapType::noext);
}
}

bool isExt(Type type) {
return type.isRef() && type.getHeapType() == HeapType::ext;
}

void visitIf(If* curr) {
// Before the lowering we could have one arm be a ref.null none and the
// other a stringref; after the lowering that is invalid, because the
// string is now extern, which has no shared ancestor with none. Fix
// that up manually in the simple case of an if arm with a null by
// correcting the null's type. This is of course wildly insufficient (we
// need selects and blocks and all other joins) but in practice this is
// enough for now. TODO extend as needed
if (curr->type.isRef() && curr->type.getHeapType() == HeapType::ext) {
auto fixArm = [](Expression* arm) {
if (auto* null = arm->dynCast<RefNull>()) {
null->finalize(HeapType::noext);
}
};
fixArm(curr->ifTrue);
fixArm(curr->ifFalse);
// If the if outputs an ext, fix up the arms to contain proper nulls for
// that type.
if (isExt(curr->type)) {
ensureNullIsExt(curr->ifTrue);
ensureNullIsExt(curr->ifFalse);
}
}

void visitStructNew(StructNew* curr) {
if (curr->type == Type::unreachable || curr->operands.empty()) {
return;
}

// If we write a none into an ext field, fix that.
auto& fields = curr->type.getHeapType().getStruct().fields;
assert(curr->operands.size() == fields.size());
for (Index i = 0; i < fields.size(); i++) {
if (isExt(fields[i].type)) {
ensureNullIsExt(curr->operands[i]);
}
}
}
};
Expand Down
105 changes: 75 additions & 30 deletions test/lit/passes/string-lowering-instructions.wast
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

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

;; CHECK: (type $struct-of-string (struct (field externref) (field i32) (field anyref)))
(type $struct-of-string (struct (field stringref) (field i32) (field anyref)))

;; CHECK: (type $struct-of-array (struct (field (ref $0))))
(type $struct-of-array (struct (field (ref $array16))))

Expand All @@ -34,58 +37,60 @@
(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 $13 (func (param externref) (result externref)))

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

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

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

;; CHECK: (type $17 (func (param externref externref externref externref)))
;; CHECK: (type $17 (func (param (ref $0))))

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

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

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

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

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

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

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

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

;; CHECK: (import "string.const" "1" (global $string.const_value (ref extern)))

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

;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $18) (param (ref null $0) i32 i32) (result (ref extern))))
;; CHECK: (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (type $19) (param (ref null $0) i32 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" "fromCodePoint" (func $fromCodePoint_17 (type $20) (param i32) (result (ref extern))))

;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $20) (param externref (ref null $0) i32) (result i32)))
;; CHECK: (import "wasm:js-string" "intoCharCodeArray" (func $intoCharCodeArray (type $21) (param externref (ref null $0) i32) (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 $2) (param externref externref) (result i32)))

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

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

;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $23) (param externref i32 i32) (result (ref extern))))
;; CHECK: (import "wasm:js-string" "substring" (func $substring (type $24) (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 $17) (param $a externref) (param $b externref) (param $c externref) (param $d externref)
;; CHECK: (func $string.as (type $18) (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 @@ -120,7 +125,7 @@
)
)

;; CHECK: (func $string.new.gc (type $16) (param $array16 (ref $0))
;; CHECK: (func $string.new.gc (type $17) (param $array16 (ref $0))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $fromCharCodeArray
;; CHECK-NEXT: (local.get $array16)
Expand All @@ -140,7 +145,7 @@
)

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

;; CHECK: (func $string.encode (type $15) (param $ref externref) (param $array16 (ref $0)) (result i32)
;; CHECK: (func $string.encode (type $16) (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 @@ -165,7 +170,7 @@
)
)

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

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

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

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

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

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

;; CHECK: (func $if.string.flip (type $11) (param $ref externref) (result externref)
;; CHECK: (func $if.string.flip (type $12) (param $ref externref) (result externref)
;; CHECK-NEXT: (if (result externref)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (then
Expand Down Expand Up @@ -366,4 +371,44 @@
)
)
)

;; CHECK: (func $struct-of-string (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $struct-of-string
;; CHECK-NEXT: (ref.null noextern)
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $struct-of-string
;; CHECK-NEXT: (global.get $string.const_value)
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $struct-of-string)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $struct-of-string
;; Test lowering of struct fields from stringref to externref.
(drop
(struct.new $struct-of-string
(ref.null none) ;; This null must be fixed to be ext.
(i32.const 10)
(ref.null none) ;; Nothing to do here (field remains anyref).
)
)
(drop
(struct.new $struct-of-string
(string.const "value") ;; Nothing to do besides change to a global.
(i32.const 10)
(ref.null none)
)
)
(drop
(struct.new_default $struct-of-string) ;; Nothing to do here.
)
)
)