Skip to content

Commit ecafbf9

Browse files
committed
Add table64 lowering pass
Changes to wasm-validator.cpp here are mostly for consistency between elem and data segment validation.
1 parent 940f457 commit ecafbf9

File tree

8 files changed

+264
-55
lines changed

8 files changed

+264
-55
lines changed

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ set(passes_SOURCES
105105
ReorderGlobals.cpp
106106
ReorderLocals.cpp
107107
ReReloop.cpp
108+
Table64Lowering.cpp
108109
TrapMode.cpp
109110
TypeGeneralizing.cpp
110111
TypeRefining.cpp

src/passes/Memory64Lowering.cpp

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -129,34 +129,39 @@ struct Memory64Lowering : public WalkerPass<PostWalker<Memory64Lowering>> {
129129
}
130130

131131
void visitDataSegment(DataSegment* segment) {
132-
if (!segment->isPassive) {
133-
if (auto* c = segment->offset->dynCast<Const>()) {
134-
c->value = Literal(static_cast<uint32_t>(c->value.geti64()));
135-
c->type = Type::i32;
136-
} else if (auto* get = segment->offset->dynCast<GlobalGet>()) {
137-
auto& module = *getModule();
138-
auto* g = module.getGlobal(get->name);
139-
if (g->imported() && g->base == MEMORY_BASE) {
140-
ImportInfo info(module);
141-
auto* memoryBase32 = info.getImportedGlobal(g->module, MEMORY_BASE32);
142-
if (!memoryBase32) {
143-
Builder builder(module);
144-
memoryBase32 = builder
145-
.makeGlobal(MEMORY_BASE32,
146-
Type::i32,
147-
builder.makeConst(int32_t(0)),
148-
Builder::Immutable)
149-
.release();
150-
memoryBase32->module = g->module;
151-
memoryBase32->base = MEMORY_BASE32;
152-
module.addGlobal(memoryBase32);
153-
}
154-
// Use this alternative import when initializing the segment.
155-
assert(memoryBase32);
156-
get->type = Type::i32;
157-
get->name = memoryBase32->name;
132+
if (segment->isPassive) {
133+
// passive segments don't have any offset to adjust
134+
return;
135+
}
136+
137+
if (auto* c = segment->offset->dynCast<Const>()) {
138+
c->value = Literal(static_cast<uint32_t>(c->value.geti64()));
139+
c->type = Type::i32;
140+
} else if (auto* get = segment->offset->dynCast<GlobalGet>()) {
141+
auto& module = *getModule();
142+
auto* g = module.getGlobal(get->name);
143+
if (g->imported() && g->base == MEMORY_BASE) {
144+
ImportInfo info(module);
145+
auto* memoryBase32 = info.getImportedGlobal(g->module, MEMORY_BASE32);
146+
if (!memoryBase32) {
147+
Builder builder(module);
148+
memoryBase32 = builder
149+
.makeGlobal(MEMORY_BASE32,
150+
Type::i32,
151+
builder.makeConst(int32_t(0)),
152+
Builder::Immutable)
153+
.release();
154+
memoryBase32->module = g->module;
155+
memoryBase32->base = MEMORY_BASE32;
156+
module.addGlobal(memoryBase32);
158157
}
158+
// Use this alternative import when initializing the segment.
159+
assert(memoryBase32);
160+
get->type = Type::i32;
161+
get->name = memoryBase32->name;
159162
}
163+
} else {
164+
WASM_UNREACHABLE("unexpected elem offset");
160165
}
161166
}
162167

src/passes/Table64Lowering.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2024 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Lowers a module with a 64-bit table to one with a 32-bit table.
19+
//
20+
// This pass can be delete once table64 is implemented in the Wasm engines:
21+
// https://github.com/WebAssembly/memory64/issues/51
22+
//
23+
24+
#include "ir/bits.h"
25+
#include "ir/import-utils.h"
26+
#include "pass.h"
27+
#include "wasm-builder.h"
28+
#include "wasm.h"
29+
30+
namespace wasm {
31+
32+
static Name TABLE_BASE("__table_base");
33+
static Name TABLE_BASE32("__table_base32");
34+
35+
struct Table64Lowering : public WalkerPass<PostWalker<Table64Lowering>> {
36+
37+
void wrapAddress64(Expression*& ptr, Name tableName) {
38+
if (ptr->type == Type::unreachable) {
39+
return;
40+
}
41+
auto& module = *getModule();
42+
auto table = module.getTable(tableName);
43+
if (table->is64()) {
44+
assert(ptr->type == Type::i64);
45+
Builder builder(module);
46+
ptr = builder.makeUnary(UnaryOp::WrapInt64, ptr);
47+
}
48+
}
49+
50+
void extendAddress64(Expression*& ptr, Name tableName) {
51+
if (ptr->type == Type::unreachable) {
52+
return;
53+
}
54+
auto& module = *getModule();
55+
auto table = module.getTable(tableName);
56+
if (table->is64()) {
57+
assert(ptr->type == Type::i64);
58+
ptr->type = Type::i32;
59+
Builder builder(module);
60+
ptr = builder.makeUnary(UnaryOp::ExtendUInt32, ptr);
61+
}
62+
}
63+
64+
void visitTableSize(TableSize* curr) {
65+
auto& module = *getModule();
66+
auto table = module.getTable(curr->table);
67+
if (table->is64()) {
68+
auto size = static_cast<Expression*>(curr);
69+
extendAddress64(size, curr->table);
70+
replaceCurrent(size);
71+
}
72+
}
73+
74+
void visitTableGrow(TableGrow* curr) {
75+
auto& module = *getModule();
76+
auto table = module.getTable(curr->table);
77+
if (table->is64()) {
78+
wrapAddress64(curr->delta, curr->table);
79+
auto size = static_cast<Expression*>(curr);
80+
std::cerr << "visitTableGrow: " << size->type << "\n";
81+
extendAddress64(size, curr->table);
82+
replaceCurrent(size);
83+
}
84+
}
85+
86+
void visitTableFill(TableFill* curr) {
87+
wrapAddress64(curr->dest, curr->table);
88+
wrapAddress64(curr->size, curr->table);
89+
}
90+
91+
void visitTableCopy(TableCopy* curr) {
92+
wrapAddress64(curr->dest, curr->destTable);
93+
wrapAddress64(curr->source, curr->sourceTable);
94+
wrapAddress64(curr->size, curr->destTable);
95+
}
96+
97+
void visitCallIndirect(CallIndirect* curr) {
98+
wrapAddress64(curr->target, curr->table);
99+
}
100+
101+
void visitTable(Table* table) {
102+
// This is visited last.
103+
if (table->is64()) {
104+
table->indexType = Type::i32;
105+
}
106+
}
107+
108+
void visitElementSegment(ElementSegment* segment) {
109+
if (segment->table.isNull()) {
110+
// Passive segments don't have any offset to update.
111+
return;
112+
}
113+
114+
if (auto* c = segment->offset->dynCast<Const>()) {
115+
c->value = Literal(static_cast<uint32_t>(c->value.geti64()));
116+
c->type = Type::i32;
117+
} else if (auto* get = segment->offset->dynCast<GlobalGet>()) {
118+
auto& module = *getModule();
119+
auto* g = module.getGlobal(get->name);
120+
if (g->imported() && g->base == TABLE_BASE) {
121+
ImportInfo info(module);
122+
auto* memoryBase32 = info.getImportedGlobal(g->module, TABLE_BASE32);
123+
if (!memoryBase32) {
124+
Builder builder(module);
125+
memoryBase32 = builder
126+
.makeGlobal(TABLE_BASE32,
127+
Type::i32,
128+
builder.makeConst(int32_t(0)),
129+
Builder::Immutable)
130+
.release();
131+
memoryBase32->module = g->module;
132+
memoryBase32->base = TABLE_BASE32;
133+
module.addGlobal(memoryBase32);
134+
}
135+
// Use this alternative import when initializing the segment.
136+
assert(memoryBase32);
137+
get->type = Type::i32;
138+
get->name = memoryBase32->name;
139+
}
140+
} else {
141+
WASM_UNREACHABLE("unexpected elem offset");
142+
}
143+
}
144+
};
145+
146+
Pass* createTable64LoweringPass() { return new Table64Lowering(); }
147+
148+
} // namespace wasm

src/passes/pass.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ void PassRegistry::registerPasses() {
251251
"lower loads and stores to a 64-bit memory to instead use a "
252252
"32-bit one",
253253
createMemory64LoweringPass);
254+
registerPass("table64-lowering",
255+
"lower 64-bit tables 32-bit ones",
256+
createTable64LoweringPass);
254257
registerPass("memory-packing",
255258
"packs memory into separate segments, skipping zeros",
256259
createMemoryPackingPass);

src/passes/passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ Pass* createStripEHPass();
166166
Pass* createStubUnsupportedJSOpsPass();
167167
Pass* createSSAifyPass();
168168
Pass* createSSAifyNoMergePass();
169+
Pass* createTable64LoweringPass();
169170
Pass* createTranslateToNewEHPass();
170171
Pass* createTrapModeClamp();
171172
Pass* createTrapModeJS();

src/wasm/wasm-validator.cpp

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3782,25 +3782,14 @@ static void validateDataSegments(Module& module, ValidationInfo& info) {
37823782
"active segment must have a valid memory name")) {
37833783
continue;
37843784
}
3785-
if (memory->is64()) {
3786-
if (!info.shouldBeEqual(segment->offset->type,
3787-
Type(Type::i64),
3788-
segment->offset,
3789-
"segment offset should be i64")) {
3790-
continue;
3791-
}
3792-
} else {
3793-
if (!info.shouldBeEqual(segment->offset->type,
3794-
Type(Type::i32),
3795-
segment->offset,
3796-
"segment offset should be i32")) {
3797-
continue;
3798-
}
3799-
}
3785+
info.shouldBeEqual(segment->offset->type,
3786+
memory->indexType,
3787+
segment->offset,
3788+
"segment offset must match memory index type");
38003789
info.shouldBeTrue(
38013790
Properties::isValidConstantExpression(module, segment->offset),
38023791
segment->offset,
3803-
"memory segment offset should be constant");
3792+
"memory segment offset must be constant");
38043793
FunctionValidator(module, &info).validate(segment->offset);
38053794
}
38063795
}
@@ -3874,31 +3863,30 @@ static void validateTables(Module& module, ValidationInfo& info) {
38743863
"elem",
38753864
"Non-nullable reference types are not yet supported for tables");
38763865

3877-
if (segment->table.is()) {
3866+
bool isPassive = !segment->table.is();
3867+
if (isPassive) {
3868+
info.shouldBeTrue(
3869+
!segment->offset, "elem", "passive segment should not have an offset");
3870+
} else {
38783871
auto table = module.getTableOrNull(segment->table);
38793872
info.shouldBeTrue(table != nullptr,
38803873
"elem",
38813874
"element segment must have a valid table name");
3882-
info.shouldBeTrue(!!segment->offset,
3883-
"elem",
3884-
"table segment offset should have an offset");
3875+
info.shouldBeTrue(
3876+
!!segment->offset, "elem", "table segment offset must have an offset");
38853877
info.shouldBeEqual(segment->offset->type,
3886-
Type(Type::i32),
3878+
table->indexType,
38873879
segment->offset,
3888-
"element segment offset should be i32");
3880+
"element segment offset must match table index type");
38893881
info.shouldBeTrue(
38903882
Properties::isValidConstantExpression(module, segment->offset),
38913883
segment->offset,
3892-
"table segment offset should be constant");
3884+
"table segment offset must be constant");
38933885
info.shouldBeTrue(
38943886
Type::isSubType(segment->type, table->type),
38953887
"elem",
38963888
"element segment type must be a subtype of the table type");
38973889
validator.validate(segment->offset);
3898-
} else {
3899-
info.shouldBeTrue(!segment->offset,
3900-
"elem",
3901-
"non-table segment offset should have no offset");
39023890
}
39033891
for (auto* expr : segment->data) {
39043892
info.shouldBeTrue(Properties::isValidConstantExpression(module, expr),

src/wasm/wasm.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,8 +851,6 @@ void TableSize::finalize() {
851851
void TableGrow::finalize() {
852852
if (delta->type == Type::unreachable || value->type == Type::unreachable) {
853853
type = Type::unreachable;
854-
} else {
855-
type = Type::i32;
856854
}
857855
}
858856

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: wasm-opt %s --enable-memory64 --enable-reference-types --enable-bulk-memory --table64-lowering -S -o - | filecheck %s
4+
5+
(module
6+
;; CHECK: (type $0 (func))
7+
8+
;; CHECK: (type $1 (func (result i64)))
9+
10+
;; CHECK: (table $t 10 100 funcref)
11+
(table $t i64 10 100 funcref)
12+
13+
(elem (table $t) (i64.const 0) funcref (ref.null func))
14+
15+
;; CHECK: (elem $0 (table $t) (i32.const 0) funcref (ref.null nofunc))
16+
17+
;; CHECK: (func $test_call_indirect
18+
;; CHECK-NEXT: (call_indirect $t (type $0)
19+
;; CHECK-NEXT: (i32.wrap_i64
20+
;; CHECK-NEXT: (i64.const 0)
21+
;; CHECK-NEXT: )
22+
;; CHECK-NEXT: )
23+
;; CHECK-NEXT: )
24+
(func $test_call_indirect
25+
(call_indirect 0 (i64.const 0))
26+
)
27+
28+
;; CHECK: (func $test_table_size (result i64)
29+
;; CHECK-NEXT: (i64.extend_i32_u
30+
;; CHECK-NEXT: (table.size $t)
31+
;; CHECK-NEXT: )
32+
;; CHECK-NEXT: )
33+
(func $test_table_size (result i64)
34+
(table.size $t)
35+
)
36+
37+
;; CHECK: (func $test_table_grow (result i64)
38+
;; CHECK-NEXT: (i64.extend_i32_u
39+
;; CHECK-NEXT: (table.grow $t
40+
;; CHECK-NEXT: (ref.null nofunc)
41+
;; CHECK-NEXT: (i32.wrap_i64
42+
;; CHECK-NEXT: (i64.const 10)
43+
;; CHECK-NEXT: )
44+
;; CHECK-NEXT: )
45+
;; CHECK-NEXT: )
46+
;; CHECK-NEXT: )
47+
(func $test_table_grow (result i64)
48+
(table.grow $t (ref.null func) (i64.const 10))
49+
)
50+
51+
;; CHECK: (func $test_table_fill
52+
;; CHECK-NEXT: (table.fill $t
53+
;; CHECK-NEXT: (i32.wrap_i64
54+
;; CHECK-NEXT: (i64.const 0)
55+
;; CHECK-NEXT: )
56+
;; CHECK-NEXT: (ref.null nofunc)
57+
;; CHECK-NEXT: (i32.wrap_i64
58+
;; CHECK-NEXT: (i64.const 10)
59+
;; CHECK-NEXT: )
60+
;; CHECK-NEXT: )
61+
;; CHECK-NEXT: )
62+
(func $test_table_fill
63+
(table.fill $t (i64.const 0) (ref.null func) (i64.const 10))
64+
)
65+
)

0 commit comments

Comments
 (0)