Skip to content

Commit fa2b2fc

Browse files
authored
wazevo(frontend): simple bounds check elimination on mem access (#1883)
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
1 parent d26cbad commit fa2b2fc

File tree

3 files changed

+161
-198
lines changed

3 files changed

+161
-198
lines changed

internal/engine/wazevo/frontend/frontend.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,17 @@ type Compiler struct {
5050
br *bytes.Reader
5151
loweringState loweringState
5252

53+
knownSafeBounds []knownSafeBound
54+
knownSafeBoundsSet []ssa.ValueID
55+
5356
execCtxPtrValue, moduleCtxPtrValue ssa.Value
5457
}
5558

59+
type knownSafeBound struct {
60+
bound uint64
61+
absoluteAddr ssa.Value
62+
}
63+
5664
// NewFrontendCompiler returns a frontend Compiler.
5765
func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData, ensureTermination bool, listenerOn bool, sourceInfo bool) *Compiler {
5866
c := &Compiler{
@@ -354,3 +362,42 @@ func SignatureForListener(wasmSig *wasm.FunctionType) (*ssa.Signature, *ssa.Sign
354362
}
355363
return beforeSig, afterSig
356364
}
365+
366+
// isBoundSafe returns true if the given value is known to be safe to access up to the given bound.
367+
func (c *Compiler) getKnownSafeBound(v ssa.ValueID) *knownSafeBound {
368+
if int(v) >= len(c.knownSafeBounds) {
369+
return nil
370+
}
371+
return &c.knownSafeBounds[v]
372+
}
373+
374+
// recordKnownSafeBound records the given safe bound for the given value.
375+
func (c *Compiler) recordKnownSafeBound(v ssa.ValueID, safeBound uint64, absoluteAddr ssa.Value) {
376+
if int(v) >= len(c.knownSafeBounds) {
377+
c.knownSafeBounds = append(c.knownSafeBounds, make([]knownSafeBound, v+1)...)
378+
}
379+
380+
if exiting := c.knownSafeBounds[v]; exiting.bound == 0 {
381+
c.knownSafeBounds[v] = knownSafeBound{
382+
bound: safeBound,
383+
absoluteAddr: absoluteAddr,
384+
}
385+
c.knownSafeBoundsSet = append(c.knownSafeBoundsSet, v)
386+
} else if safeBound > exiting.bound {
387+
c.knownSafeBounds[v].bound = safeBound
388+
}
389+
}
390+
391+
// clearSafeBounds clears the known safe bounds. This must be called
392+
// after the compilation of each block.
393+
func (c *Compiler) clearSafeBounds() {
394+
for _, v := range c.knownSafeBoundsSet {
395+
ptr := &c.knownSafeBounds[v]
396+
ptr.bound = 0
397+
}
398+
c.knownSafeBoundsSet = c.knownSafeBoundsSet[:0]
399+
}
400+
401+
func (k *knownSafeBound) valid() bool {
402+
return k != nil && k.bound > 0
403+
}

internal/engine/wazevo/frontend/frontend_test.go

Lines changed: 82 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,14 +1019,8 @@ blk0: (exec_ctx:i64, module_ctx:i64, v2:i32, v3:i32)
10191019
v9:i64 = Load module_ctx, 0x8
10201020
v10:i64 = Iadd v9, v5
10211021
Store v3, v10, 0x0
1022-
v11:i64 = Iconst_64 0x4
1023-
v12:i64 = UExtend v2, 32->64
1024-
v13:i64 = Iadd v12, v11
1025-
v14:i32 = Icmp lt_u, v6, v13
1026-
ExitIfTrue v14, exec_ctx, memory_out_of_bounds
1027-
v15:i64 = Iadd v9, v12
1028-
v16:i32 = Load v15, 0x0
1029-
Jump blk_ret, v16
1022+
v11:i32 = Load v10, 0x0
1023+
Jump blk_ret, v11
10301024
`,
10311025
},
10321026
{
@@ -1142,191 +1136,44 @@ blk0: (exec_ctx:i64, module_ctx:i64, v2:i32)
11421136
v13:i64 = Iadd v12, v11
11431137
v14:i32 = Icmp lt_u, v5, v13
11441138
ExitIfTrue v14, exec_ctx, memory_out_of_bounds
1145-
v15:i64 = Iadd v8, v12
1146-
v16:i64 = Load v15, 0x0
1147-
v17:i64 = Iconst_64 0x4
1148-
v18:i64 = UExtend v2, 32->64
1149-
v19:i64 = Iadd v18, v17
1150-
v20:i32 = Icmp lt_u, v5, v19
1151-
ExitIfTrue v20, exec_ctx, memory_out_of_bounds
1152-
v21:i64 = Iadd v8, v18
1153-
v22:f32 = Load v21, 0x0
1154-
v23:i64 = Iconst_64 0x8
1139+
v15:i64 = Load v9, 0x0
1140+
v16:f32 = Load v9, 0x0
1141+
v17:f64 = Load v9, 0x0
1142+
v18:i64 = Iconst_64 0x13
1143+
v19:i64 = UExtend v2, 32->64
1144+
v20:i64 = Iadd v19, v18
1145+
v21:i32 = Icmp lt_u, v5, v20
1146+
ExitIfTrue v21, exec_ctx, memory_out_of_bounds
1147+
v22:i32 = Load v9, 0xf
1148+
v23:i64 = Iconst_64 0x17
11551149
v24:i64 = UExtend v2, 32->64
11561150
v25:i64 = Iadd v24, v23
11571151
v26:i32 = Icmp lt_u, v5, v25
11581152
ExitIfTrue v26, exec_ctx, memory_out_of_bounds
1159-
v27:i64 = Iadd v8, v24
1160-
v28:f64 = Load v27, 0x0
1161-
v29:i64 = Iconst_64 0x13
1162-
v30:i64 = UExtend v2, 32->64
1163-
v31:i64 = Iadd v30, v29
1164-
v32:i32 = Icmp lt_u, v5, v31
1165-
ExitIfTrue v32, exec_ctx, memory_out_of_bounds
1166-
v33:i64 = Iadd v8, v30
1167-
v34:i32 = Load v33, 0xf
1168-
v35:i64 = Iconst_64 0x17
1169-
v36:i64 = UExtend v2, 32->64
1170-
v37:i64 = Iadd v36, v35
1171-
v38:i32 = Icmp lt_u, v5, v37
1172-
ExitIfTrue v38, exec_ctx, memory_out_of_bounds
1173-
v39:i64 = Iadd v8, v36
1174-
v40:i64 = Load v39, 0xf
1175-
v41:i64 = Iconst_64 0x13
1176-
v42:i64 = UExtend v2, 32->64
1177-
v43:i64 = Iadd v42, v41
1178-
v44:i32 = Icmp lt_u, v5, v43
1179-
ExitIfTrue v44, exec_ctx, memory_out_of_bounds
1180-
v45:i64 = Iadd v8, v42
1181-
v46:f32 = Load v45, 0xf
1182-
v47:i64 = Iconst_64 0x17
1183-
v48:i64 = UExtend v2, 32->64
1184-
v49:i64 = Iadd v48, v47
1185-
v50:i32 = Icmp lt_u, v5, v49
1186-
ExitIfTrue v50, exec_ctx, memory_out_of_bounds
1187-
v51:i64 = Iadd v8, v48
1188-
v52:f64 = Load v51, 0xf
1189-
v53:i64 = Iconst_64 0x1
1190-
v54:i64 = UExtend v2, 32->64
1191-
v55:i64 = Iadd v54, v53
1192-
v56:i32 = Icmp lt_u, v5, v55
1193-
ExitIfTrue v56, exec_ctx, memory_out_of_bounds
1194-
v57:i64 = Iadd v8, v54
1195-
v58:i32 = Sload8 v57, 0x0
1196-
v59:i64 = Iconst_64 0x10
1197-
v60:i64 = UExtend v2, 32->64
1198-
v61:i64 = Iadd v60, v59
1199-
v62:i32 = Icmp lt_u, v5, v61
1200-
ExitIfTrue v62, exec_ctx, memory_out_of_bounds
1201-
v63:i64 = Iadd v8, v60
1202-
v64:i32 = Sload8 v63, 0xf
1203-
v65:i64 = Iconst_64 0x1
1204-
v66:i64 = UExtend v2, 32->64
1205-
v67:i64 = Iadd v66, v65
1206-
v68:i32 = Icmp lt_u, v5, v67
1207-
ExitIfTrue v68, exec_ctx, memory_out_of_bounds
1208-
v69:i64 = Iadd v8, v66
1209-
v70:i32 = Uload8 v69, 0x0
1210-
v71:i64 = Iconst_64 0x10
1211-
v72:i64 = UExtend v2, 32->64
1212-
v73:i64 = Iadd v72, v71
1213-
v74:i32 = Icmp lt_u, v5, v73
1214-
ExitIfTrue v74, exec_ctx, memory_out_of_bounds
1215-
v75:i64 = Iadd v8, v72
1216-
v76:i32 = Uload8 v75, 0xf
1217-
v77:i64 = Iconst_64 0x2
1218-
v78:i64 = UExtend v2, 32->64
1219-
v79:i64 = Iadd v78, v77
1220-
v80:i32 = Icmp lt_u, v5, v79
1221-
ExitIfTrue v80, exec_ctx, memory_out_of_bounds
1222-
v81:i64 = Iadd v8, v78
1223-
v82:i32 = Sload16 v81, 0x0
1224-
v83:i64 = Iconst_64 0x11
1225-
v84:i64 = UExtend v2, 32->64
1226-
v85:i64 = Iadd v84, v83
1227-
v86:i32 = Icmp lt_u, v5, v85
1228-
ExitIfTrue v86, exec_ctx, memory_out_of_bounds
1229-
v87:i64 = Iadd v8, v84
1230-
v88:i32 = Sload16 v87, 0xf
1231-
v89:i64 = Iconst_64 0x2
1232-
v90:i64 = UExtend v2, 32->64
1233-
v91:i64 = Iadd v90, v89
1234-
v92:i32 = Icmp lt_u, v5, v91
1235-
ExitIfTrue v92, exec_ctx, memory_out_of_bounds
1236-
v93:i64 = Iadd v8, v90
1237-
v94:i32 = Uload16 v93, 0x0
1238-
v95:i64 = Iconst_64 0x11
1239-
v96:i64 = UExtend v2, 32->64
1240-
v97:i64 = Iadd v96, v95
1241-
v98:i32 = Icmp lt_u, v5, v97
1242-
ExitIfTrue v98, exec_ctx, memory_out_of_bounds
1243-
v99:i64 = Iadd v8, v96
1244-
v100:i32 = Uload16 v99, 0xf
1245-
v101:i64 = Iconst_64 0x1
1246-
v102:i64 = UExtend v2, 32->64
1247-
v103:i64 = Iadd v102, v101
1248-
v104:i32 = Icmp lt_u, v5, v103
1249-
ExitIfTrue v104, exec_ctx, memory_out_of_bounds
1250-
v105:i64 = Iadd v8, v102
1251-
v106:i64 = Sload8 v105, 0x0
1252-
v107:i64 = Iconst_64 0x10
1253-
v108:i64 = UExtend v2, 32->64
1254-
v109:i64 = Iadd v108, v107
1255-
v110:i32 = Icmp lt_u, v5, v109
1256-
ExitIfTrue v110, exec_ctx, memory_out_of_bounds
1257-
v111:i64 = Iadd v8, v108
1258-
v112:i64 = Sload8 v111, 0xf
1259-
v113:i64 = Iconst_64 0x1
1260-
v114:i64 = UExtend v2, 32->64
1261-
v115:i64 = Iadd v114, v113
1262-
v116:i32 = Icmp lt_u, v5, v115
1263-
ExitIfTrue v116, exec_ctx, memory_out_of_bounds
1264-
v117:i64 = Iadd v8, v114
1265-
v118:i64 = Uload8 v117, 0x0
1266-
v119:i64 = Iconst_64 0x10
1267-
v120:i64 = UExtend v2, 32->64
1268-
v121:i64 = Iadd v120, v119
1269-
v122:i32 = Icmp lt_u, v5, v121
1270-
ExitIfTrue v122, exec_ctx, memory_out_of_bounds
1271-
v123:i64 = Iadd v8, v120
1272-
v124:i64 = Uload8 v123, 0xf
1273-
v125:i64 = Iconst_64 0x2
1274-
v126:i64 = UExtend v2, 32->64
1275-
v127:i64 = Iadd v126, v125
1276-
v128:i32 = Icmp lt_u, v5, v127
1277-
ExitIfTrue v128, exec_ctx, memory_out_of_bounds
1278-
v129:i64 = Iadd v8, v126
1279-
v130:i64 = Sload16 v129, 0x0
1280-
v131:i64 = Iconst_64 0x11
1281-
v132:i64 = UExtend v2, 32->64
1282-
v133:i64 = Iadd v132, v131
1283-
v134:i32 = Icmp lt_u, v5, v133
1284-
ExitIfTrue v134, exec_ctx, memory_out_of_bounds
1285-
v135:i64 = Iadd v8, v132
1286-
v136:i64 = Sload16 v135, 0xf
1287-
v137:i64 = Iconst_64 0x2
1288-
v138:i64 = UExtend v2, 32->64
1289-
v139:i64 = Iadd v138, v137
1290-
v140:i32 = Icmp lt_u, v5, v139
1291-
ExitIfTrue v140, exec_ctx, memory_out_of_bounds
1292-
v141:i64 = Iadd v8, v138
1293-
v142:i64 = Uload16 v141, 0x0
1294-
v143:i64 = Iconst_64 0x11
1295-
v144:i64 = UExtend v2, 32->64
1296-
v145:i64 = Iadd v144, v143
1297-
v146:i32 = Icmp lt_u, v5, v145
1298-
ExitIfTrue v146, exec_ctx, memory_out_of_bounds
1299-
v147:i64 = Iadd v8, v144
1300-
v148:i64 = Uload16 v147, 0xf
1301-
v149:i64 = Iconst_64 0x4
1302-
v150:i64 = UExtend v2, 32->64
1303-
v151:i64 = Iadd v150, v149
1304-
v152:i32 = Icmp lt_u, v5, v151
1305-
ExitIfTrue v152, exec_ctx, memory_out_of_bounds
1306-
v153:i64 = Iadd v8, v150
1307-
v154:i64 = Sload32 v153, 0x0
1308-
v155:i64 = Iconst_64 0x13
1309-
v156:i64 = UExtend v2, 32->64
1310-
v157:i64 = Iadd v156, v155
1311-
v158:i32 = Icmp lt_u, v5, v157
1312-
ExitIfTrue v158, exec_ctx, memory_out_of_bounds
1313-
v159:i64 = Iadd v8, v156
1314-
v160:i64 = Sload32 v159, 0xf
1315-
v161:i64 = Iconst_64 0x4
1316-
v162:i64 = UExtend v2, 32->64
1317-
v163:i64 = Iadd v162, v161
1318-
v164:i32 = Icmp lt_u, v5, v163
1319-
ExitIfTrue v164, exec_ctx, memory_out_of_bounds
1320-
v165:i64 = Iadd v8, v162
1321-
v166:i64 = Uload32 v165, 0x0
1322-
v167:i64 = Iconst_64 0x13
1323-
v168:i64 = UExtend v2, 32->64
1324-
v169:i64 = Iadd v168, v167
1325-
v170:i32 = Icmp lt_u, v5, v169
1326-
ExitIfTrue v170, exec_ctx, memory_out_of_bounds
1327-
v171:i64 = Iadd v8, v168
1328-
v172:i64 = Uload32 v171, 0xf
1329-
Jump blk_ret, v10, v16, v22, v28, v34, v40, v46, v52, v58, v64, v70, v76, v82, v88, v94, v100, v106, v112, v118, v124, v130, v136, v142, v148, v154, v160, v166, v172
1153+
v27:i64 = Load v9, 0xf
1154+
v28:f32 = Load v9, 0xf
1155+
v29:f64 = Load v9, 0xf
1156+
v30:i32 = Sload8 v9, 0x0
1157+
v31:i32 = Sload8 v9, 0xf
1158+
v32:i32 = Uload8 v9, 0x0
1159+
v33:i32 = Uload8 v9, 0xf
1160+
v34:i32 = Sload16 v9, 0x0
1161+
v35:i32 = Sload16 v9, 0xf
1162+
v36:i32 = Uload16 v9, 0x0
1163+
v37:i32 = Uload16 v9, 0xf
1164+
v38:i64 = Sload8 v9, 0x0
1165+
v39:i64 = Sload8 v9, 0xf
1166+
v40:i64 = Uload8 v9, 0x0
1167+
v41:i64 = Uload8 v9, 0xf
1168+
v42:i64 = Sload16 v9, 0x0
1169+
v43:i64 = Sload16 v9, 0xf
1170+
v44:i64 = Uload16 v9, 0x0
1171+
v45:i64 = Uload16 v9, 0xf
1172+
v46:i64 = Sload32 v9, 0x0
1173+
v47:i64 = Sload32 v9, 0xf
1174+
v48:i64 = Uload32 v9, 0x0
1175+
v49:i64 = Uload32 v9, 0xf
1176+
Jump blk_ret, v10, v15, v16, v17, v22, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49
13301177
`,
13311178
},
13321179
{
@@ -1934,3 +1781,49 @@ func TestCompiler_declareSignatures(t *testing.T) {
19341781
}
19351782
})
19361783
}
1784+
1785+
func TestCompiler_recordKnownSafeBound(t *testing.T) {
1786+
c := &Compiler{}
1787+
c.recordKnownSafeBound(1, 99, 9999)
1788+
require.Equal(t, 1, len(c.knownSafeBoundsSet))
1789+
require.True(t, c.getKnownSafeBound(1).valid())
1790+
require.Equal(t, uint64(99), c.getKnownSafeBound(1).bound)
1791+
require.Equal(t, ssa.Value(9999), c.getKnownSafeBound(1).absoluteAddr)
1792+
1793+
c.recordKnownSafeBound(1, 150, 9999)
1794+
require.Equal(t, 1, len(c.knownSafeBoundsSet))
1795+
require.Equal(t, uint64(150), c.getKnownSafeBound(1).bound)
1796+
1797+
c.recordKnownSafeBound(5, 666, 54321)
1798+
require.Equal(t, 2, len(c.knownSafeBoundsSet))
1799+
require.Equal(t, uint64(666), c.getKnownSafeBound(5).bound)
1800+
require.Equal(t, ssa.Value(54321), c.getKnownSafeBound(5).absoluteAddr)
1801+
}
1802+
1803+
func TestCompiler_getKnownSafeBound(t *testing.T) {
1804+
c := &Compiler{
1805+
knownSafeBounds: []knownSafeBound{
1806+
{}, {bound: 2134},
1807+
},
1808+
}
1809+
require.Nil(t, c.getKnownSafeBound(5))
1810+
require.Nil(t, c.getKnownSafeBound(12345))
1811+
require.False(t, c.getKnownSafeBound(0).valid())
1812+
require.True(t, c.getKnownSafeBound(1).valid())
1813+
}
1814+
1815+
func TestCompiler_clearSafeBounds(t *testing.T) {
1816+
c := &Compiler{}
1817+
c.knownSafeBounds = []knownSafeBound{{bound: 1}, {}, {bound: 2}, {}, {}, {bound: 3}}
1818+
c.knownSafeBoundsSet = []ssa.ValueID{0, 2, 5}
1819+
c.clearSafeBounds()
1820+
require.Equal(t, 0, len(c.knownSafeBoundsSet))
1821+
require.Equal(t, []knownSafeBound{{}, {}, {}, {}, {}, {}}, c.knownSafeBounds)
1822+
}
1823+
1824+
func TestKnownSafeBound_valid(t *testing.T) {
1825+
k := &knownSafeBound{bound: 10, absoluteAddr: 12345}
1826+
require.True(t, k.valid())
1827+
k.bound = 0
1828+
require.False(t, k.valid())
1829+
}

0 commit comments

Comments
 (0)