authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-24 21:06:52-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-07-24 21:06:52-04:00
log653c851e6233a105cb151602517d0222b1128ea7
treec8c30217017f3003303b077230fd02a798335151
parent7b8cb881df7e034a8626caabf355055ee81a0fef
parent30376a82b2c1b13047ff3b48391fcda44183e129
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9446 from Luukdegram/stage2-air-wasm

stage2: wasm - Use `br_table` when possible for switch

2 files changed, 244 insertions(+), 114 deletions(-)

src/codegen/wasm.zig+184-52
......@@ -979,10 +979,15 @@ pub const Context = struct {
979979 .valtype1 = try self.typeToValtype(ty),
980980 });
981981 try writer.writeByte(wasm.opcode(opcode));
982 const int_info = ty.intInfo(self.target);
982983 // write constant
983 switch (ty.intInfo(self.target).signedness) {
984 switch (int_info.signedness) {
984985 .signed => try leb.writeILEB128(writer, value.toSignedInt()),
985 .unsigned => try leb.writeILEB128(writer, value.toUnsignedInt()),
986 .unsigned => switch (int_info.bits) {
987 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, value.toUnsignedInt()))),
988 33...64 => try leb.writeILEB128(writer, @bitCast(i64, value.toUnsignedInt())),
989 else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}),
990 },
986991 }
987992 },
988993 .Bool => {
......@@ -1079,6 +1084,42 @@ pub const Context = struct {
10791084 }
10801085 }
10811086
1087 /// Returns a `Value` as a signed 32 bit value.
1088 /// It's illegale to provide a value with a type that cannot be represented
1089 /// as an integer value.
1090 fn valueAsI32(self: Context, val: Value, ty: Type) i32 {
1091 switch (ty.zigTypeTag()) {
1092 .Enum => {
1093 if (val.castTag(.enum_field_index)) |field_index| {
1094 switch (ty.tag()) {
1095 .enum_simple => return @bitCast(i32, field_index.data),
1096 .enum_full, .enum_nonexhaustive => {
1097 const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
1098 if (enum_full.values.count() != 0) {
1099 const tag_val = enum_full.values.keys()[field_index.data];
1100 return self.valueAsI32(tag_val, enum_full.tag_ty);
1101 } else return @bitCast(i32, field_index.data);
1102 },
1103 else => unreachable,
1104 }
1105 } else {
1106 var int_tag_buffer: Type.Payload.Bits = undefined;
1107 const int_tag_ty = ty.intTagType(&int_tag_buffer);
1108 return self.valueAsI32(val, int_tag_ty);
1109 }
1110 },
1111 .Int => switch (ty.intInfo(self.target).signedness) {
1112 .signed => return @truncate(i32, val.toSignedInt()),
1113 .unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())),
1114 },
1115 .ErrorSet => {
1116 const error_index = self.global_error_set.get(val.getError().?).?;
1117 return @bitCast(i32, error_index);
1118 },
1119 else => unreachable, // Programmer called this function for an illegal type
1120 }
1121 }
1122
10821123 fn airBlock(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
10831124 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
10841125 const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
......@@ -1271,60 +1312,151 @@ pub const Context = struct {
12711312 }
12721313
12731314 fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
1315 // result type is always 'noreturn'
1316 const blocktype = wasm.block_empty;
12741317 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1275 const extra = self.air.extraData(Air.SwitchBr, pl_op.payload);
1276 const cases = self.air.extra[extra.end..][0..extra.data.cases_len];
1277 const else_body = self.air.extra[extra.end + cases.len ..][0..extra.data.else_body_len];
1278
12791318 const target = self.resolveInst(pl_op.operand);
12801319 const target_ty = self.air.typeOf(pl_op.operand);
1281 const valtype = try self.typeToValtype(target_ty);
1282 // result type is always 'noreturn'
1283 const blocktype = wasm.block_empty;
1320 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
1321 var extra_index: usize = switch_br.end;
1322 var case_i: u32 = 0;
1323
1324 // a list that maps each value with its value and body based on the order inside the list.
1325 const CaseValue = struct { integer: i32, value: Value };
1326 var case_list = try std.ArrayList(struct {
1327 values: []const CaseValue,
1328 body: []const Air.Inst.Index,
1329 }).initCapacity(self.gpa, switch_br.data.cases_len);
1330 defer for (case_list.items) |case| {
1331 self.gpa.free(case.values);
1332 } else case_list.deinit();
1333
1334 var lowest: i32 = 0;
1335 var highest: i32 = 0;
1336 while (case_i < switch_br.data.cases_len) : (case_i += 1) {
1337 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
1338 const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
1339 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
1340 extra_index = case.end + items.len + case_body.len;
1341 const values = try self.gpa.alloc(CaseValue, items.len);
1342 errdefer self.gpa.free(values);
1343
1344 for (items) |ref, i| {
1345 const item_val = self.air.value(ref).?;
1346 const int_val = self.valueAsI32(item_val, target_ty);
1347 if (int_val < lowest) {
1348 lowest = int_val;
1349 }
1350 if (int_val > highest) {
1351 highest = int_val;
1352 }
1353 values[i] = .{ .integer = int_val, .value = item_val };
1354 }
1355
1356 case_list.appendAssumeCapacity(.{ .values = values, .body = case_body });
1357 try self.startBlock(.block, blocktype, null);
1358 }
1359
1360 // When the highest and lowest values are seperated by '50',
1361 // we define it as sparse and use an if/else-chain, rather than a jump table.
1362 // When the target is an integer size larger than u32, we have no way to use the value
1363 // as an index, therefore we also use an if/else-chain for those cases.
1364 // TODO: Benchmark this to find a proper value, LLVM seems to draw the line at '40~45'.
1365 const is_sparse = highest - lowest > 50 or target_ty.bitSize(self.target) > 32;
1366
1367 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
1368 const has_else_body = else_body.len != 0;
1369 if (has_else_body) {
1370 try self.startBlock(.block, blocktype, null);
1371 }
1372
1373 if (!is_sparse) {
1374 // Generate the jump table 'br_table' when the prongs are not sparse.
1375 // The value 'target' represents the index into the table.
1376 // Each index in the table represents a label to the branch
1377 // to jump to.
1378 try self.startBlock(.block, blocktype, null);
1379 try self.emitWValue(target);
1380 if (lowest < 0) {
1381 // since br_table works using indexes, starting from '0', we must ensure all values
1382 // we put inside, are atleast 0.
1383 try self.code.append(wasm.opcode(.i32_const));
1384 try leb.writeILEB128(self.code.writer(), lowest * -1);
1385 try self.code.append(wasm.opcode(.i32_add));
1386 }
1387 try self.code.append(wasm.opcode(.br_table));
1388 const depth = highest - lowest + @boolToInt(has_else_body);
1389 try leb.writeILEB128(self.code.writer(), depth);
1390 while (lowest <= highest) : (lowest += 1) {
1391 // idx represents the branch we jump to
1392 const idx = blk: {
1393 for (case_list.items) |case, idx| {
1394 for (case.values) |case_value| {
1395 if (case_value.integer == lowest) break :blk @intCast(u32, idx);
1396 }
1397 }
1398 break :blk if (has_else_body) case_i else unreachable;
1399 };
1400 try leb.writeULEB128(self.code.writer(), idx);
1401 } else if (has_else_body) {
1402 try leb.writeULEB128(self.code.writer(), @as(u32, case_i)); // default branch
1403 }
1404 try self.endBlock();
1405 }
12841406
1285 _ = valtype;
1286 _ = blocktype;
1287 _ = target;
1288 _ = else_body;
1289 return self.fail("TODO implement wasm codegen for switch", .{});
1290 //const signedness: std.builtin.Signedness = blk: {
1291 // // by default we tell the operand type is unsigned (i.e. bools and enum values)
1292 // if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1293
1294 // // incase of an actual integer, we emit the correct signedness
1295 // break :blk target_ty.intInfo(self.target).signedness;
1296 //};
1297 //for (cases) |case_idx| {
1298 // const case = self.air.extraData(Air.SwitchBr.Case, case_idx);
1299 // const case_body = self.air.extra[case.end..][0..case.data.body_len];
1300
1301 // // create a block for each case, when the condition does not match we break out of it
1302 // try self.startBlock(.block, blocktype, null);
1303 // try self.emitWValue(target);
1304
1305 // const val = self.air.value(case.data.item).?;
1306 // try self.emitConstant(val, target_ty);
1307 // const opcode = buildOpcode(.{
1308 // .valtype1 = valtype,
1309 // .op = .ne, // not equal because we jump out the block if it does not match the condition
1310 // .signedness = signedness,
1311 // });
1312 // try self.code.append(wasm.opcode(opcode));
1313 // try self.code.append(wasm.opcode(.br_if));
1314 // try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1315
1316 // // emit our block code
1317 // try self.genBody(case_body);
1318
1319 // // end the block we created earlier
1320 // try self.endBlock();
1321 //}
1322
1323 //// finally, emit the else case if it exists. Here we will not have to
1324 //// check for a condition, so also no need to emit a block.
1325 //try self.genBody(else_body);
1326
1327 //return .none;
1407 const signedness: std.builtin.Signedness = blk: {
1408 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1409 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1410
1411 // incase of an actual integer, we emit the correct signedness
1412 break :blk target_ty.intInfo(self.target).signedness;
1413 };
1414
1415 for (case_list.items) |case| {
1416 // when sparse, we use if/else-chain, so emit conditional checks
1417 if (is_sparse) {
1418 // for single value prong we can emit a simple if
1419 if (case.values.len == 1) {
1420 try self.emitWValue(target);
1421 try self.emitConstant(case.values[0].value, target_ty);
1422 const opcode = buildOpcode(.{
1423 .valtype1 = try self.typeToValtype(target_ty),
1424 .op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
1425 .signedness = signedness,
1426 });
1427 try self.code.append(wasm.opcode(opcode));
1428 try self.code.append(wasm.opcode(.br_if));
1429 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1430 } else {
1431 // in multi-value prongs we must check if any prongs match the target value.
1432 try self.startBlock(.block, blocktype, null);
1433 for (case.values) |value| {
1434 try self.emitWValue(target);
1435 try self.emitConstant(value.value, target_ty);
1436 const opcode = buildOpcode(.{
1437 .valtype1 = try self.typeToValtype(target_ty),
1438 .op = .eq,
1439 .signedness = signedness,
1440 });
1441 try self.code.append(wasm.opcode(opcode));
1442 try self.code.append(wasm.opcode(.br_if));
1443 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1444 }
1445 // value did not match any of the prong values
1446 try self.code.append(wasm.opcode(.br));
1447 try leb.writeULEB128(self.code.writer(), @as(u32, 1));
1448 try self.endBlock();
1449 }
1450 }
1451 try self.genBody(case.body);
1452 try self.endBlock();
1453 }
1454
1455 if (has_else_body) {
1456 try self.genBody(else_body);
1457 try self.endBlock();
1458 }
1459 return .none;
13281460 }
13291461
13301462 fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
test/stage2/wasm.zig+60-62
......@@ -479,68 +479,66 @@ pub fn addCases(ctx: *TestContext) !void {
479479 , "30\n");
480480 }
481481
482 // This test case is disabled until the codegen for switch is reworked
483 // to take advantage of br_table rather than a series of br_if opcodes.
484 //{
485 // var case = ctx.exe("wasm switch", wasi);
486
487 // case.addCompareOutput(
488 // \\pub export fn _start() u32 {
489 // \\ var val: u32 = 1;
490 // \\ var a: u32 = switch (val) {
491 // \\ 0, 1 => 2,
492 // \\ 2 => 3,
493 // \\ 3 => 4,
494 // \\ else => 5,
495 // \\ };
496 // \\
497 // \\ return a;
498 // \\}
499 // , "2\n");
500
501 // case.addCompareOutput(
502 // \\pub export fn _start() u32 {
503 // \\ var val: u32 = 2;
504 // \\ var a: u32 = switch (val) {
505 // \\ 0, 1 => 2,
506 // \\ 2 => 3,
507 // \\ 3 => 4,
508 // \\ else => 5,
509 // \\ };
510 // \\
511 // \\ return a;
512 // \\}
513 // , "3\n");
514
515 // case.addCompareOutput(
516 // \\pub export fn _start() u32 {
517 // \\ var val: u32 = 10;
518 // \\ var a: u32 = switch (val) {
519 // \\ 0, 1 => 2,
520 // \\ 2 => 3,
521 // \\ 3 => 4,
522 // \\ else => 5,
523 // \\ };
524 // \\
525 // \\ return a;
526 // \\}
527 // , "5\n");
528
529 // case.addCompareOutput(
530 // \\const MyEnum = enum { One, Two, Three };
531 // \\
532 // \\pub export fn _start() u32 {
533 // \\ var val: MyEnum = .Two;
534 // \\ var a: u32 = switch (val) {
535 // \\ .One => 1,
536 // \\ .Two => 2,
537 // \\ .Three => 3,
538 // \\ };
539 // \\
540 // \\ return a;
541 // \\}
542 // , "2\n");
543 //}
482 {
483 var case = ctx.exe("wasm switch", wasi);
484
485 case.addCompareOutput(
486 \\pub export fn _start() u32 {
487 \\ var val: u32 = 1;
488 \\ var a: u32 = switch (val) {
489 \\ 0, 1 => 2,
490 \\ 2 => 3,
491 \\ 3 => 4,
492 \\ else => 5,
493 \\ };
494 \\
495 \\ return a;
496 \\}
497 , "2\n");
498
499 case.addCompareOutput(
500 \\pub export fn _start() u32 {
501 \\ var val: u32 = 2;
502 \\ var a: u32 = switch (val) {
503 \\ 0, 1 => 2,
504 \\ 2 => 3,
505 \\ 3 => 4,
506 \\ else => 5,
507 \\ };
508 \\
509 \\ return a;
510 \\}
511 , "3\n");
512
513 case.addCompareOutput(
514 \\pub export fn _start() u32 {
515 \\ var val: u32 = 10;
516 \\ var a: u32 = switch (val) {
517 \\ 0, 1 => 2,
518 \\ 2 => 3,
519 \\ 3 => 4,
520 \\ else => 5,
521 \\ };
522 \\
523 \\ return a;
524 \\}
525 , "5\n");
526
527 case.addCompareOutput(
528 \\const MyEnum = enum { One, Two, Three };
529 \\
530 \\pub export fn _start() u32 {
531 \\ var val: MyEnum = .Two;
532 \\ var a: u32 = switch (val) {
533 \\ .One => 1,
534 \\ .Two => 2,
535 \\ .Three => 3,
536 \\ };
537 \\
538 \\ return a;
539 \\}
540 , "2\n");
541 }
544542
545543 {
546544 var case = ctx.exe("wasm error unions", wasi);