authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-07-23 16:41:04+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-07-24 19:49:25+02:00
logcb41f0e58d4788806aac0bfece2e298c3c79b737
tree7aae4e19719a02c211f2134fabd4e84d660a49e0
parentad38fc11470ad3c2e063ad5739d4f4e84dd495f1
signaturelock-open Commit is signed but in an unrecognized format.

switchbr: When prongs are sparse values, use if/else-chain


1 files changed, 69 insertions(+), 28 deletions(-)

src/codegen/wasm.zig+69-28
......@@ -1275,12 +1275,17 @@ pub const Context = struct {
12751275 const blocktype = wasm.block_empty;
12761276 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
12771277 const target = self.resolveInst(pl_op.operand);
1278 const target_ty = self.air.typeOf(pl_op.operand);
12781279 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
12791280 var extra_index: usize = switch_br.end;
12801281 var case_i: u32 = 0;
12811282
12821283 // a map that maps each value with its index and body
1283 var map = std.AutoArrayHashMap(u32, struct { index: u32, body: []const Air.Inst.Index }).init(self.gpa);
1284 var map = std.AutoArrayHashMap(u32, struct {
1285 index: u32,
1286 body: []const Air.Inst.Index,
1287 value: Value,
1288 }).init(self.gpa);
12841289 defer map.deinit();
12851290
12861291 var lowest: u32 = 0;
......@@ -1292,51 +1297,87 @@ pub const Context = struct {
12921297 extra_index = case.end + items.len + case_body.len;
12931298
12941299 for (items) |ref| {
1295 const item_val = @intCast(u32, self.air.value(ref).?.toUnsignedInt());
1296 if (item_val < lowest) {
1297 lowest = item_val;
1300 const item_val = self.air.value(ref).?;
1301 // safe to truncate the values as we only use them when
1302 // the target's bits is 32 or lower.
1303 const int_val = @truncate(u32, item_val.toUnsignedInt());
1304 if (int_val < lowest) {
1305 lowest = int_val;
12981306 }
1299 if (item_val > highest) {
1300 highest = item_val;
1307 if (int_val > highest) {
1308 highest = int_val;
13011309 }
1302 try map.put(item_val, .{ .index = case_i, .body = case_body });
1310 try map.put(int_val, .{ .index = case_i, .body = case_body, .value = item_val });
13031311 }
13041312
13051313 try self.startBlock(.block, blocktype, null);
13061314 }
13071315
1316 // When the highest and lowest values are seperated by '50',
1317 // we define it as sparse and use an if/else-chain, rather than a jump table.
1318 // When the target is an integer size larger than u32, we have no way to use the value
1319 // as an index, therefore we also use an if/else-chain for those cases.
1320 // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
1321 const is_sparse = target_ty.intInfo(self.target).bits > 32 or highest - lowest > 50;
1322
13081323 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
1309 if (else_body.len != 0) {
1324 const has_else_body = else_body.len != 0;
1325 if (has_else_body) {
13101326 try self.startBlock(.block, blocktype, null);
13111327 }
13121328
1313 // Generate the jump table 'br_table'.
1314 // The value 'target' represents the index into the table.
1315 // Each index in the table represents a label to the branch
1316 // to jump to.
1317 try self.startBlock(.block, blocktype, null);
1318 try self.emitWValue(target);
1319 try self.code.append(wasm.opcode(.br_table));
1320 try leb.writeULEB128(self.code.writer(), highest - lowest + 1);
1321 while (lowest <= highest) : (lowest += 1) {
1322 const idx = if (map.get(lowest)) |value| blk: {
1323 break :blk value.index + 1;
1324 } else 0;
1325 try leb.writeULEB128(self.code.writer(), idx);
1326 } else if (else_body.len != 0) {
1327 try leb.writeULEB128(self.code.writer(), @as(u32, 0)); // default branch
1328 }
1329 try self.endBlock();
1330
1331 if (else_body.len != 0) {
1332 try self.genBody(else_body);
1329 if (!is_sparse) {
1330 // Generate the jump table 'br_table' when the prongs are not sparse.
1331 // The value 'target' represents the index into the table.
1332 // Each index in the table represents a label to the branch
1333 // to jump to.
1334 try self.startBlock(.block, blocktype, null);
1335 try self.emitWValue(target);
1336 try self.code.append(wasm.opcode(.br_table));
1337 const depth = highest - lowest + @boolToInt(has_else_body);
1338 try leb.writeULEB128(self.code.writer(), depth);
1339 while (lowest <= highest) : (lowest += 1) {
1340 const idx = if (map.get(lowest)) |value| blk: {
1341 break :blk value.index;
1342 } else if (has_else_body) case_i else unreachable;
1343 try leb.writeULEB128(self.code.writer(), idx);
1344 } else if (has_else_body) {
1345 try leb.writeULEB128(self.code.writer(), @as(u32, case_i)); // default branch
1346 }
13331347 try self.endBlock();
13341348 }
13351349
1350 const signedness: std.builtin.Signedness = blk: {
1351 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1352 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1353
1354 // incase of an actual integer, we emit the correct signedness
1355 break :blk target_ty.intInfo(self.target).signedness;
1356 };
1357
13361358 for (map.values()) |val| {
1359 // when sparse, we use if/else-chain, so emit conditional checks
1360 if (is_sparse) {
1361 try self.emitWValue(target);
1362 try self.emitConstant(val.value, target_ty);
1363 const opcode = buildOpcode(.{
1364 .valtype1 = try self.typeToValtype(target_ty),
1365 .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
1366 .signedness = signedness,
1367 });
1368 try self.code.append(wasm.opcode(opcode));
1369 try self.code.append(wasm.opcode(.br_if));
1370 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1371 }
13371372 try self.genBody(val.body);
13381373 try self.endBlock();
13391374 }
1375
1376 if (has_else_body) {
1377 try self.genBody(else_body);
1378 try self.endBlock();
1379 }
1380
13401381 return .none;
13411382 }
13421383