| ... | ... | @@ -1275,12 +1275,17 @@ pub const Context = struct { |
| 1275 | 1275 | const blocktype = wasm.block_empty; |
| 1276 | 1276 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; |
| 1277 | 1277 | const target = self.resolveInst(pl_op.operand); |
| 1278 | const target_ty = self.air.typeOf(pl_op.operand); |
| 1278 | 1279 | const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload); |
| 1279 | 1280 | var extra_index: usize = switch_br.end; |
| 1280 | 1281 | var case_i: u32 = 0; |
| 1281 | 1282 | |
| 1282 | 1283 | // 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); |
| 1284 | 1289 | defer map.deinit(); |
| 1285 | 1290 | |
| 1286 | 1291 | var lowest: u32 = 0; |
| ... | ... | @@ -1292,51 +1297,87 @@ pub const Context = struct { |
| 1292 | 1297 | extra_index = case.end + items.len + case_body.len; |
| 1293 | 1298 | |
| 1294 | 1299 | 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; |
| 1298 | 1306 | } |
| 1299 | | if (item_val > highest) { |
| 1300 | | highest = item_val; |
| 1307 | if (int_val > highest) { |
| 1308 | highest = int_val; |
| 1301 | 1309 | } |
| 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 }); |
| 1303 | 1311 | } |
| 1304 | 1312 | |
| 1305 | 1313 | try self.startBlock(.block, blocktype, null); |
| 1306 | 1314 | } |
| 1307 | 1315 | |
| 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 | |
| 1308 | 1323 | 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) { |
| 1310 | 1326 | try self.startBlock(.block, blocktype, null); |
| 1311 | 1327 | } |
| 1312 | 1328 | |
| 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 | } |
| 1333 | 1347 | try self.endBlock(); |
| 1334 | 1348 | } |
| 1335 | 1349 | |
| 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 | |
| 1336 | 1358 | 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 | } |
| 1337 | 1372 | try self.genBody(val.body); |
| 1338 | 1373 | try self.endBlock(); |
| 1339 | 1374 | } |
| 1375 | |
| 1376 | if (has_else_body) { |
| 1377 | try self.genBody(else_body); |
| 1378 | try self.endBlock(); |
| 1379 | } |
| 1380 | |
| 1340 | 1381 | return .none; |
| 1341 | 1382 | } |
| 1342 | 1383 | |