authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-20 18:55:49-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-20 18:55:49-04:00
log0267abfe9b14b07dcf98f06218416f4b8aaeda48
tree931173cbe28a0af22d4a2b627a9cfd1d7fadf475
parent884547f177d1f15934da4276cd301acd93cb1657
parent0ac56e7f3aba39f96ec9ca3fa3b253c9654979cf
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8847 from Luukdegram/wasm-struct-switch

stage2: wasm - Structs and switch support

2 files changed, 219 insertions(+), 13 deletions(-)

src/codegen/wasm.zig+103-13
......@@ -31,6 +31,9 @@ const WValue = union(enum) {
3131 code_offset: usize,
3232 /// The label of the block, used by breaks to find its relative distance
3333 block_idx: u32,
34 /// Used for variables that create multiple locals on the stack when allocated
35 /// such as structs and optionals.
36 multi_value: u32,
3437};
3538
3639/// Wasm ops, but without input/output/signedness information
......@@ -556,7 +559,7 @@ pub const Context = struct {
556559 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
557560 return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
558561 },
559 .Bool, .Pointer => wasm.Valtype.i32,
562 .Bool, .Pointer, .Struct => wasm.Valtype.i32,
560563 .Enum => switch (ty.tag()) {
561564 .enum_simple => wasm.Valtype.i32,
562565 else => self.typeToValtype(
......@@ -587,8 +590,9 @@ pub const Context = struct {
587590 fn emitWValue(self: *Context, val: WValue) InnerError!void {
588591 const writer = self.code.writer();
589592 switch (val) {
590 .block_idx => unreachable,
591 .none, .code_offset => {},
593 .block_idx => unreachable, // block_idx cannot be referenced
594 .multi_value => unreachable, // multi_value can never be written directly, and must be accessed individually
595 .none, .code_offset => {}, // no-op
592596 .local => |idx| {
593597 try writer.writeByte(wasm.opcode(.local_get));
594598 try leb.writeULEB128(writer, idx);
......@@ -714,8 +718,8 @@ pub const Context = struct {
714718 .add => self.genBinOp(inst.castTag(.add).?, .add),
715719 .alloc => self.genAlloc(inst.castTag(.alloc).?),
716720 .arg => self.genArg(inst.castTag(.arg).?),
717 .bitcast => self.genBitcast(inst.castTag(.bitcast).?),
718721 .bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"),
722 .bitcast => self.genBitcast(inst.castTag(.bitcast).?),
719723 .bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"),
720724 .block => self.genBlock(inst.castTag(.block).?),
721725 .bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"),
......@@ -740,7 +744,9 @@ pub const Context = struct {
740744 .ret => self.genRet(inst.castTag(.ret).?),
741745 .retvoid => WValue.none,
742746 .store => self.genStore(inst.castTag(.store).?),
747 .struct_field_ptr => self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?),
743748 .sub => self.genBinOp(inst.castTag(.sub).?, .sub),
749 .switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),
744750 .unreach => self.genUnreachable(inst.castTag(.unreach).?),
745751 .xor => self.genBinOp(inst.castTag(.xor).?, .xor),
746752 else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),
......@@ -794,11 +800,30 @@ pub const Context = struct {
794800
795801 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
796802 const elem_type = inst.base.ty.elemType();
797 const valtype = try self.genValtype(inst.base.src, elem_type);
798 try self.locals.append(self.gpa, valtype);
799
800 defer self.local_index += 1;
801 return WValue{ .local = self.local_index };
803 const initial_index = self.local_index;
804
805 switch (elem_type.zigTypeTag()) {
806 .Struct => {
807 // for each struct field, generate a local
808 const struct_data: *Module.Struct = elem_type.castTag(.@"struct").?.data;
809 try self.locals.ensureCapacity(self.gpa, self.locals.items.len + struct_data.fields.count());
810 for (struct_data.fields.items()) |entry| {
811 const val_type = try self.genValtype(
812 .{ .node_offset = struct_data.node_offset },
813 entry.value.ty,
814 );
815 self.locals.appendAssumeCapacity(val_type);
816 self.local_index += 1;
817 }
818 return WValue{ .multi_value = initial_index };
819 },
820 else => {
821 const valtype = try self.genValtype(inst.base.src, elem_type);
822 try self.locals.append(self.gpa, valtype);
823 self.local_index += 1;
824 return WValue{ .local = initial_index };
825 },
826 }
802827 }
803828
804829 fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
......@@ -806,10 +831,20 @@ pub const Context = struct {
806831
807832 const lhs = self.resolveInst(inst.lhs);
808833 const rhs = self.resolveInst(inst.rhs);
809 try self.emitWValue(rhs);
810834
811 try writer.writeByte(wasm.opcode(.local_set));
812 try leb.writeULEB128(writer, lhs.local);
835 switch (lhs) {
836 // When assigning a value to a multi_value such as a struct,
837 // we simply assign the local_index to the rhs one.
838 // This allows us to update struct fields without having to individually
839 // set each local as each field's index will be calculated off the struct's base index
840 .multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
841 .local => |local| {
842 try self.emitWValue(rhs);
843 try writer.writeByte(wasm.opcode(.local_set));
844 try leb.writeULEB128(writer, lhs.local);
845 },
846 else => unreachable,
847 }
813848 return .none;
814849 }
815850
......@@ -827,6 +862,14 @@ pub const Context = struct {
827862 const lhs = self.resolveInst(inst.lhs);
828863 const rhs = self.resolveInst(inst.rhs);
829864
865 // it's possible for both lhs and/or rhs to return an offset as well,
866 // in which case we return the first offset occurance we find.
867 const offset = blk: {
868 if (lhs == .code_offset) break :blk lhs.code_offset;
869 if (rhs == .code_offset) break :blk rhs.code_offset;
870 break :blk self.code.items.len;
871 };
872
830873 try self.emitWValue(lhs);
831874 try self.emitWValue(rhs);
832875
......@@ -836,7 +879,7 @@ pub const Context = struct {
836879 .signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned,
837880 });
838881 try self.code.append(wasm.opcode(opcode));
839 return .none;
882 return WValue{ .code_offset = offset };
840883 }
841884
842885 fn emitConstant(self: *Context, src: LazySrcLoc, value: Value, ty: Type) InnerError!void {
......@@ -1090,4 +1133,51 @@ pub const Context = struct {
10901133 fn genBitcast(self: *Context, bitcast: *Inst.UnOp) InnerError!WValue {
10911134 return self.resolveInst(bitcast.operand);
10921135 }
1136
1137 fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
1138 const struct_ptr = self.resolveInst(inst.struct_ptr);
1139
1140 return WValue{ .local = struct_ptr.multi_value + @intCast(u32, inst.field_index) };
1141 }
1142
1143 fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {
1144 const target = self.resolveInst(inst.target);
1145 const target_ty = inst.target.ty;
1146 const valtype = try self.typeToValtype(.{ .node_offset = 0 }, target_ty);
1147 const blocktype = try self.genBlockType(inst.base.src, inst.base.ty);
1148
1149 const signedness: std.builtin.Signedness = blk: {
1150 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1151 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1152
1153 // incase of an actual integer, we emit the correct signedness
1154 break :blk target_ty.intInfo(self.target).signedness;
1155 };
1156 for (inst.cases) |case| {
1157 // create a block for each case, when the condition does not match we break out of it
1158 try self.startBlock(.block, blocktype, null);
1159 try self.emitWValue(target);
1160 try self.emitConstant(.{ .node_offset = 0 }, case.item, target_ty);
1161 const opcode = buildOpcode(.{
1162 .valtype1 = valtype,
1163 .op = .ne, // not equal because we jump out the block if it does not match the condition
1164 .signedness = signedness,
1165 });
1166 try self.code.append(wasm.opcode(opcode));
1167 try self.code.append(wasm.opcode(.br_if));
1168 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1169
1170 // emit our block code
1171 try self.genBody(case.body);
1172
1173 // end the block we created earlier
1174 try self.endBlock();
1175 }
1176
1177 // finally, emit the else case if it exists. Here we will not have to
1178 // check for a condition, so also no need to emit a block.
1179 try self.genBody(inst.else_body);
1180
1181 return .none;
1182 }
10931183};
test/stage2/wasm.zig+116
......@@ -419,4 +419,120 @@ pub fn addCases(ctx: *TestContext) !void {
419419 \\}
420420 , "2\n");
421421 }
422
423 {
424 var case = ctx.exe("wasm structs", wasi);
425
426 case.addCompareOutput(
427 \\const Example = struct { x: u32 };
428 \\
429 \\pub export fn _start() u32 {
430 \\ var example: Example = .{ .x = 5 };
431 \\ return example.x;
432 \\}
433 , "5\n");
434
435 case.addCompareOutput(
436 \\const Example = struct { x: u32 };
437 \\
438 \\pub export fn _start() u32 {
439 \\ var example: Example = .{ .x = 5 };
440 \\ example.x = 10;
441 \\ return example.x;
442 \\}
443 , "10\n");
444
445 case.addCompareOutput(
446 \\const Example = struct { x: u32, y: u32 };
447 \\
448 \\pub export fn _start() u32 {
449 \\ var example: Example = .{ .x = 5, .y = 10 };
450 \\ return example.y + example.x;
451 \\}
452 , "15\n");
453
454 case.addCompareOutput(
455 \\const Example = struct { x: u32, y: u32 };
456 \\
457 \\pub export fn _start() u32 {
458 \\ var example: Example = .{ .x = 5, .y = 10 };
459 \\ var example2: Example = .{ .x = 10, .y = 20 };
460 \\
461 \\ example = example2;
462 \\ return example.y + example.x;
463 \\}
464 , "30\n");
465
466 case.addCompareOutput(
467 \\const Example = struct { x: u32, y: u32 };
468 \\
469 \\pub export fn _start() u32 {
470 \\ var example: Example = .{ .x = 5, .y = 10 };
471 \\
472 \\ example = .{ .x = 10, .y = 20 };
473 \\ return example.y + example.x;
474 \\}
475 , "30\n");
476 }
477
478 {
479 var case = ctx.exe("wasm switch", wasi);
480
481 case.addCompareOutput(
482 \\pub export fn _start() u32 {
483 \\ var val: u32 = 1;
484 \\ var a: u32 = switch (val) {
485 \\ 0, 1 => 2,
486 \\ 2 => 3,
487 \\ 3 => 4,
488 \\ else => 5,
489 \\ };
490 \\
491 \\ return a;
492 \\}
493 , "2\n");
494
495 case.addCompareOutput(
496 \\pub export fn _start() u32 {
497 \\ var val: u32 = 2;
498 \\ var a: u32 = switch (val) {
499 \\ 0, 1 => 2,
500 \\ 2 => 3,
501 \\ 3 => 4,
502 \\ else => 5,
503 \\ };
504 \\
505 \\ return a;
506 \\}
507 , "3\n");
508
509 case.addCompareOutput(
510 \\pub export fn _start() u32 {
511 \\ var val: u32 = 10;
512 \\ var a: u32 = switch (val) {
513 \\ 0, 1 => 2,
514 \\ 2 => 3,
515 \\ 3 => 4,
516 \\ else => 5,
517 \\ };
518 \\
519 \\ return a;
520 \\}
521 , "5\n");
522
523 case.addCompareOutput(
524 \\const MyEnum = enum { One, Two, Three };
525 \\
526 \\pub export fn _start() u32 {
527 \\ var val: MyEnum = .Two;
528 \\ var a: u32 = switch (val) {
529 \\ .One => 1,
530 \\ .Two => 2,
531 \\ .Three => 3,
532 \\ };
533 \\
534 \\ return a;
535 \\}
536 , "2\n");
537 }
422538}