authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-02-01 12:29:22-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-02-01 12:29:22-08:00
log1517ed0a5ede7da476fdffa8bfe74dab3d1e3810
treeb229bdbac93fd8b24b9ac6a8b2c654db67508886
parentc0685458a2f9463bf3c2276f9b5d9ca4b3157cd7
parentcc46c1b9024beefdd82ce8abd07e8849a72db20c
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7895 from Luukdegram/wasm-control-flow

stage2: wasm control flow

3 files changed, 370 insertions(+), 71 deletions(-)

src/codegen/wasm.zig+267-68
......@@ -4,6 +4,7 @@ const ArrayList = std.ArrayList;
44const assert = std.debug.assert;
55const leb = std.leb;
66const mem = std.mem;
7const wasm = std.wasm;
78
89const Module = @import("../Module.zig");
910const Decl = Module.Decl;
......@@ -12,6 +13,7 @@ const Inst = ir.Inst;
1213const Type = @import("../type.zig").Type;
1314const Value = @import("../value.zig").Value;
1415const Compilation = @import("../Compilation.zig");
16const AnyMCValue = @import("../codegen.zig").AnyMCValue;
1517
1618/// Wasm Value, created when generating an instruction
1719const WValue = union(enum) {
......@@ -20,23 +22,14 @@ const WValue = union(enum) {
2022 local: u32,
2123 /// Instruction holding a constant `Value`
2224 constant: *Inst,
23 /// Block label
25 /// Offset position in the list of bytecode instructions
26 code_offset: usize,
27 /// The label of the block, used by breaks to find its relative distance
2428 block_idx: u32,
2529};
2630
2731/// Hashmap to store generated `WValue` for each `Inst`
28pub const ValueTable = std.AutoHashMap(*Inst, WValue);
29
30/// Using a given `Type`, returns the corresponding wasm value type
31fn genValtype(ty: Type) ?u8 {
32 return switch (ty.tag()) {
33 .f32 => 0x7D,
34 .f64 => 0x7C,
35 .u32, .i32 => 0x7F,
36 .u64, .i64 => 0x7E,
37 else => null,
38 };
39}
32pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
4033
4134/// Code represents the `Code` section of wasm that
4235/// belongs to a function
......@@ -58,13 +51,25 @@ pub const Context = struct {
5851 local_index: u32 = 0,
5952 /// If codegen fails, an error messages will be allocated and saved in `err_msg`
6053 err_msg: *Module.ErrorMsg,
54 /// Current block depth. Used to calculate the relative difference between a break
55 /// and block
56 block_depth: u32 = 0,
57 /// List of all locals' types generated throughout this declaration
58 /// used to emit locals count at start of 'code' section.
59 locals: std.ArrayListUnmanaged(u8),
6160
6261 const InnerError = error{
6362 OutOfMemory,
6463 CodegenFail,
6564 };
6665
67 /// Sets `err_msg` on `Context` and returns `error.CodegenFail` which is caught in link/Wasm.zig
66 pub fn deinit(self: *Context) void {
67 self.values.deinit(self.gpa);
68 self.locals.deinit(self.gpa);
69 self.* = undefined;
70 }
71
72 /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
6873 fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError {
6974 self.err_msg = try Module.ErrorMsg.create(self.gpa, .{
7075 .file_scope = self.decl.getFileScope(),
......@@ -85,13 +90,35 @@ pub const Context = struct {
8590 return self.values.get(inst).?; // Instruction does not dominate all uses!
8691 }
8792
93 /// Using a given `Type`, returns the corresponding wasm value type
94 fn genValtype(self: *Context, src: usize, ty: Type) InnerError!u8 {
95 return switch (ty.tag()) {
96 .f32 => wasm.valtype(.f32),
97 .f64 => wasm.valtype(.f64),
98 .u32, .i32 => wasm.valtype(.i32),
99 .u64, .i64 => wasm.valtype(.i64),
100 else => self.fail(src, "TODO - Wasm genValtype for type '{s}'", .{ty.tag()}),
101 };
102 }
103
104 /// Using a given `Type`, returns the corresponding wasm value type
105 /// Differently from `genValtype` this also allows `void` to create a block
106 /// with no return type
107 fn genBlockType(self: *Context, src: usize, ty: Type) InnerError!u8 {
108 return switch (ty.tag()) {
109 .void, .noreturn => wasm.block_empty,
110 else => self.genValtype(src, ty),
111 };
112 }
113
88114 /// Writes the bytecode depending on the given `WValue` in `val`
89115 fn emitWValue(self: *Context, val: WValue) InnerError!void {
90116 const writer = self.code.writer();
91117 switch (val) {
92 .none, .block_idx => {},
118 .block_idx => unreachable,
119 .none, .code_offset => {},
93120 .local => |idx| {
94 try writer.writeByte(0x20); // local.get
121 try writer.writeByte(wasm.opcode(.local_get));
95122 try leb.writeULEB128(writer, idx);
96123 },
97124 .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), // creates a new constant onto the stack
......@@ -102,8 +129,7 @@ pub const Context = struct {
102129 const ty = self.decl.typed_value.most_recent.typed_value.ty;
103130 const writer = self.func_type_data.writer();
104131
105 // functype magic
106 try writer.writeByte(0x60);
132 try writer.writeByte(wasm.function_type);
107133
108134 // param types
109135 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
......@@ -112,8 +138,8 @@ pub const Context = struct {
112138 defer self.gpa.free(params);
113139 ty.fnParamTypes(params);
114140 for (params) |param_type| {
115 const val_type = genValtype(param_type) orelse
116 return self.fail(self.decl.src(), "TODO: Wasm codegen - arg type value for type '{s}'", .{param_type.tag()});
141 // Can we maybe get the source index of each param?
142 const val_type = try self.genValtype(self.decl.src(), param_type);
117143 try writer.writeByte(val_type);
118144 }
119145 }
......@@ -124,8 +150,8 @@ pub const Context = struct {
124150 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
125151 else => |ret_type| {
126152 try leb.writeULEB128(writer, @as(u32, 1));
127 const val_type = genValtype(return_type) orelse
128 return self.fail(self.decl.src(), "TODO: Wasm codegen - return type value for type '{s}'", .{ret_type});
153 // Can we maybe get the source index of the return type?
154 const val_type = try self.genValtype(self.decl.src(), return_type);
129155 try writer.writeByte(val_type);
130156 },
131157 }
......@@ -137,40 +163,33 @@ pub const Context = struct {
137163 try self.genFunctype();
138164 const writer = self.code.writer();
139165
140 // Reserve space to write the size after generating the code
141 try self.code.resize(5);
166 // Reserve space to write the size after generating the code as well as space for locals count
167 try self.code.resize(10);
142168
143169 // Write instructions
144170 // TODO: check for and handle death of instructions
145171 const tv = self.decl.typed_value.most_recent.typed_value;
146172 const mod_fn = tv.val.castTag(.function).?.data;
173 try self.genBody(mod_fn.body);
147174
148 var locals = std.ArrayList(u8).init(self.gpa);
149 defer locals.deinit();
150
151 for (mod_fn.body.instructions) |inst| {
152 if (inst.tag != .alloc) continue;
153
154 const alloc: *Inst.NoOp = inst.castTag(.alloc).?;
155 const elem_type = alloc.base.ty.elemType();
156
157 const wasm_type = genValtype(elem_type) orelse
158 return self.fail(inst.src, "TODO: Wasm codegen - valtype for type '{s}'", .{elem_type.tag()});
159
160 try locals.append(wasm_type);
161 }
175 // finally, write our local types at the 'offset' position
176 {
177 leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
162178
163 try leb.writeULEB128(writer, @intCast(u32, locals.items.len));
179 // offset into 'code' section where we will put our locals types
180 var local_offset: usize = 10;
164181
165 // emit the actual locals amount
166 for (locals.items) |local| {
167 try leb.writeULEB128(writer, @as(u32, 1));
168 try leb.writeULEB128(writer, local); // valtype
182 // emit the actual locals amount
183 for (self.locals.items) |local| {
184 var buf: [6]u8 = undefined;
185 leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
186 buf[5] = local;
187 try self.code.insertSlice(local_offset, &buf);
188 local_offset += 6;
189 }
169190 }
170191
171 try self.genBody(mod_fn.body);
172
173 try writer.writeByte(0x0B); // end
192 try writer.writeByte(wasm.opcode(.end));
174193
175194 // Fill in the size of the generated code to the reserved space at the
176195 // beginning of the buffer.
......@@ -183,10 +202,20 @@ pub const Context = struct {
183202 .add => self.genAdd(inst.castTag(.add).?),
184203 .alloc => self.genAlloc(inst.castTag(.alloc).?),
185204 .arg => self.genArg(inst.castTag(.arg).?),
205 .block => self.genBlock(inst.castTag(.block).?),
206 .br => self.genBr(inst.castTag(.br).?),
186207 .call => self.genCall(inst.castTag(.call).?),
208 .cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq),
209 .cmp_gte => self.genCmp(inst.castTag(.cmp_gte).?, .gte),
210 .cmp_gt => self.genCmp(inst.castTag(.cmp_gt).?, .gt),
211 .cmp_lte => self.genCmp(inst.castTag(.cmp_lte).?, .lte),
212 .cmp_lt => self.genCmp(inst.castTag(.cmp_lt).?, .lt),
213 .cmp_neq => self.genCmp(inst.castTag(.cmp_neq).?, .neq),
214 .condbr => self.genCondBr(inst.castTag(.condbr).?),
187215 .constant => unreachable,
188216 .dbg_stmt => WValue.none,
189217 .load => self.genLoad(inst.castTag(.load).?),
218 .loop => self.genLoop(inst.castTag(.loop).?),
190219 .ret => self.genRet(inst.castTag(.ret).?),
191220 .retvoid => WValue.none,
192221 .store => self.genStore(inst.castTag(.store).?),
......@@ -197,7 +226,7 @@ pub const Context = struct {
197226 fn genBody(self: *Context, body: ir.Body) InnerError!void {
198227 for (body.instructions) |inst| {
199228 const result = try self.genInst(inst);
200 try self.values.putNoClobber(inst, result);
229 try self.values.putNoClobber(self.gpa, inst, result);
201230 }
202231 }
203232
......@@ -205,7 +234,7 @@ pub const Context = struct {
205234 // TODO: Implement tail calls
206235 const operand = self.resolveInst(inst.operand);
207236 try self.emitWValue(operand);
208 return WValue.none;
237 return .none;
209238 }
210239
211240 fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue {
......@@ -219,7 +248,7 @@ pub const Context = struct {
219248 try self.emitWValue(arg_val);
220249 }
221250
222 try self.code.append(0x10); // call
251 try self.code.append(wasm.opcode(.call));
223252
224253 // The function index immediate argument will be filled in using this data
225254 // in link.Wasm.flush().
......@@ -228,10 +257,14 @@ pub const Context = struct {
228257 .decl = target,
229258 });
230259
231 return WValue.none;
260 return .none;
232261 }
233262
234263 fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
264 const elem_type = inst.base.ty.elemType();
265 const valtype = try self.genValtype(inst.base.src, elem_type);
266 try self.locals.append(self.gpa, valtype);
267
235268 defer self.local_index += 1;
236269 return WValue{ .local = self.local_index };
237270 }
......@@ -243,15 +276,13 @@ pub const Context = struct {
243276 const rhs = self.resolveInst(inst.rhs);
244277 try self.emitWValue(rhs);
245278
246 try writer.writeByte(0x21); // local.set
279 try writer.writeByte(wasm.opcode(.local_set));
247280 try leb.writeULEB128(writer, lhs.local);
248 return WValue.none;
281 return .none;
249282 }
250283
251284 fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
252 const operand = self.resolveInst(inst.operand);
253 try self.emitWValue(operand);
254 return WValue.none;
285 return self.resolveInst(inst.operand);
255286 }
256287
257288 fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue {
......@@ -267,44 +298,44 @@ pub const Context = struct {
267298 try self.emitWValue(lhs);
268299 try self.emitWValue(rhs);
269300
270 const opcode: u8 = switch (inst.base.ty.tag()) {
271 .u32, .i32 => 0x6A, //i32.add
272 .u64, .i64 => 0x7C, //i64.add
273 .f32 => 0x92, //f32.add
274 .f64 => 0xA0, //f64.add
301 const opcode: wasm.Opcode = switch (inst.base.ty.tag()) {
302 .u32, .i32 => .i32_add,
303 .u64, .i64 => .i64_add,
304 .f32 => .f32_add,
305 .f64 => .f64_add,
275306 else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}),
276307 };
277308
278 try self.code.append(opcode);
279 return WValue.none;
309 try self.code.append(wasm.opcode(opcode));
310 return .none;
280311 }
281312
282313 fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void {
283314 const writer = self.code.writer();
284315 switch (inst.base.ty.tag()) {
285316 .u32 => {
286 try writer.writeByte(0x41); // i32.const
317 try writer.writeByte(wasm.opcode(.i32_const));
287318 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
288319 },
289320 .i32 => {
290 try writer.writeByte(0x41); // i32.const
321 try writer.writeByte(wasm.opcode(.i32_const));
291322 try leb.writeILEB128(writer, inst.val.toSignedInt());
292323 },
293324 .u64 => {
294 try writer.writeByte(0x42); // i64.const
325 try writer.writeByte(wasm.opcode(.i64_const));
295326 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
296327 },
297328 .i64 => {
298 try writer.writeByte(0x42); // i64.const
329 try writer.writeByte(wasm.opcode(.i64_const));
299330 try leb.writeILEB128(writer, inst.val.toSignedInt());
300331 },
301332 .f32 => {
302 try writer.writeByte(0x43); // f32.const
333 try writer.writeByte(wasm.opcode(.f32_const));
303334 // TODO: enforce LE byte order
304335 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
305336 },
306337 .f64 => {
307 try writer.writeByte(0x44); // f64.const
338 try writer.writeByte(wasm.opcode(.f64_const));
308339 // TODO: enforce LE byte order
309340 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
310341 },
......@@ -312,4 +343,172 @@ pub const Context = struct {
312343 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}),
313344 }
314345 }
346
347 fn genBlock(self: *Context, block: *Inst.Block) InnerError!WValue {
348 const block_ty = try self.genBlockType(block.base.src, block.base.ty);
349
350 try self.startBlock(.block, block_ty, null);
351 block.codegen = .{
352 // we don't use relocs, so using `relocs` is illegal behaviour.
353 .relocs = undefined,
354 // Here we set the current block idx, so breaks know the depth to jump
355 // to when breaking out.
356 .mcv = @bitCast(AnyMCValue, WValue{ .block_idx = self.block_depth }),
357 };
358 try self.genBody(block.body);
359 try self.endBlock();
360
361 return .none;
362 }
363
364 /// appends a new wasm block to the code section and increases the `block_depth` by 1
365 fn startBlock(self: *Context, block_type: wasm.Opcode, valtype: u8, with_offset: ?usize) !void {
366 self.block_depth += 1;
367 if (with_offset) |offset| {
368 try self.code.insert(offset, wasm.opcode(block_type));
369 try self.code.insert(offset + 1, valtype);
370 } else {
371 try self.code.append(wasm.opcode(block_type));
372 try self.code.append(valtype);
373 }
374 }
375
376 /// Ends the current wasm block and decreases the `block_depth` by 1
377 fn endBlock(self: *Context) !void {
378 try self.code.append(wasm.opcode(.end));
379 self.block_depth -= 1;
380 }
381
382 fn genLoop(self: *Context, loop: *Inst.Loop) InnerError!WValue {
383 const loop_ty = try self.genBlockType(loop.base.src, loop.base.ty);
384
385 try self.startBlock(.loop, loop_ty, null);
386 try self.genBody(loop.body);
387
388 // breaking to the index of a loop block will continue the loop instead
389 try self.code.append(wasm.opcode(.br));
390 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
391
392 try self.endBlock();
393
394 return .none;
395 }
396
397 fn genCondBr(self: *Context, condbr: *Inst.CondBr) InnerError!WValue {
398 const condition = self.resolveInst(condbr.condition);
399 const writer = self.code.writer();
400
401 // TODO: Handle death instructions for then and else body
402
403 // insert blocks at the position of `offset` so
404 // the condition can jump to it
405 const offset = condition.code_offset;
406 const block_ty = try self.genBlockType(condbr.base.src, condbr.base.ty);
407 try self.startBlock(.block, block_ty, offset);
408
409 // we inserted the block in front of the condition
410 // so now check if condition matches. If not, break outside this block
411 // and continue with the then codepath
412 try writer.writeByte(wasm.opcode(.br_if));
413 try leb.writeULEB128(writer, @as(u32, 0));
414
415 try self.genBody(condbr.else_body);
416 try self.endBlock();
417
418 // Outer block that matches the condition
419 try self.genBody(condbr.then_body);
420
421 return .none;
422 }
423
424 fn genCmp(self: *Context, inst: *Inst.BinOp, op: std.math.CompareOperator) InnerError!WValue {
425 const ty = inst.lhs.ty.tag();
426
427 // save offset, so potential conditions can insert blocks in front of
428 // the comparison that we can later jump back to
429 const offset = self.code.items.len;
430
431 const lhs = self.resolveInst(inst.lhs);
432 const rhs = self.resolveInst(inst.rhs);
433
434 try self.emitWValue(lhs);
435 try self.emitWValue(rhs);
436
437 const opcode_maybe: ?wasm.Opcode = switch (op) {
438 .lt => @as(?wasm.Opcode, switch (ty) {
439 .i32 => .i32_lt_s,
440 .u32 => .i32_lt_u,
441 .i64 => .i64_lt_s,
442 .u64 => .i64_lt_u,
443 .f32 => .f32_lt,
444 .f64 => .f64_lt,
445 else => null,
446 }),
447 .lte => @as(?wasm.Opcode, switch (ty) {
448 .i32 => .i32_le_s,
449 .u32 => .i32_le_u,
450 .i64 => .i64_le_s,
451 .u64 => .i64_le_u,
452 .f32 => .f32_le,
453 .f64 => .f64_le,
454 else => null,
455 }),
456 .eq => @as(?wasm.Opcode, switch (ty) {
457 .i32, .u32 => .i32_eq,
458 .i64, .u64 => .i64_eq,
459 .f32 => .f32_eq,
460 .f64 => .f64_eq,
461 else => null,
462 }),
463 .gte => @as(?wasm.Opcode, switch (ty) {
464 .i32 => .i32_ge_s,
465 .u32 => .i32_ge_u,
466 .i64 => .i64_ge_s,
467 .u64 => .i64_ge_u,
468 .f32 => .f32_ge,
469 .f64 => .f64_ge,
470 else => null,
471 }),
472 .gt => @as(?wasm.Opcode, switch (ty) {
473 .i32 => .i32_gt_s,
474 .u32 => .i32_gt_u,
475 .i64 => .i64_gt_s,
476 .u64 => .i64_gt_u,
477 .f32 => .f32_gt,
478 .f64 => .f64_gt,
479 else => null,
480 }),
481 .neq => @as(?wasm.Opcode, switch (ty) {
482 .i32, .u32 => .i32_ne,
483 .i64, .u64 => .i64_ne,
484 .f32 => .f32_ne,
485 .f64 => .f64_ne,
486 else => null,
487 }),
488 };
489
490 const opcode = opcode_maybe orelse
491 return self.fail(inst.base.src, "TODO - Wasm genCmp for type '{s}' and operator '{s}'", .{ ty, @tagName(op) });
492
493 try self.code.append(wasm.opcode(opcode));
494 return WValue{ .code_offset = offset };
495 }
496
497 fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue {
498 // of operand has codegen bits we should break with a value
499 if (br.operand.ty.hasCodeGenBits()) {
500 const operand = self.resolveInst(br.operand);
501 try self.emitWValue(operand);
502 }
503
504 // every block contains a `WValue` with its block index.
505 // We then determine how far we have to jump to it by substracting it from current block depth
506 const wvalue = @bitCast(WValue, br.block.codegen.mcv);
507 const idx: u32 = self.block_depth - wvalue.block_idx;
508 const writer = self.code.writer();
509 try writer.writeByte(wasm.opcode(.br));
510 try leb.writeULEB128(writer, idx);
511
512 return .none;
513 }
315514};
src/link/Wasm.zig+11-3
......@@ -103,13 +103,14 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
103103
104104 var context = codegen.Context{
105105 .gpa = self.base.allocator,
106 .values = codegen.ValueTable.init(self.base.allocator),
106 .values = .{},
107107 .code = managed_code,
108108 .func_type_data = managed_functype,
109109 .decl = decl,
110110 .err_msg = undefined,
111 .locals = .{},
111112 };
112 defer context.values.deinit();
113 defer context.deinit();
113114
114115 // generate the 'code' section for the function declaration
115116 context.gen() catch |err| switch (err) {
......@@ -121,6 +122,13 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
121122 else => |e| return err,
122123 };
123124
125 // as locals are patched afterwards, the offsets of funcidx's are off,
126 // here we update them to correct them
127 for (decl.fn_link.wasm.?.idx_refs.items) |*func| {
128 // For each local, add 6 bytes (count + type)
129 func.offset += @intCast(u32, context.locals.items.len * 6);
130 }
131
124132 fn_data.functype = context.func_type_data.toUnmanaged();
125133 fn_data.code = context.code.toUnmanaged();
126134}
......@@ -237,7 +245,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
237245 try writer.writeAll(fn_data.code.items[current..idx_ref.offset]);
238246 current = idx_ref.offset;
239247 // Use a fixed width here to make calculating the code size
240 // in codegen.wasm.genCode() simpler.
248 // in codegen.wasm.gen() simpler.
241249 var buf: [5]u8 = undefined;
242250 leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
243251 try writer.writeAll(&buf);
test/stage2/wasm.zig+92
......@@ -122,4 +122,96 @@ pub fn addCases(ctx: *TestContext) !void {
122122 \\}
123123 , "35\n");
124124 }
125
126 {
127 var case = ctx.exe("wasm conditions", wasi);
128
129 case.addCompareOutput(
130 \\export fn _start() u32 {
131 \\ var i: u32 = 5;
132 \\ if (i > @as(u32, 4)) {
133 \\ i += 10;
134 \\ }
135 \\ return i;
136 \\}
137 , "15\n");
138
139 case.addCompareOutput(
140 \\export fn _start() u32 {
141 \\ var i: u32 = 5;
142 \\ if (i < @as(u32, 4)) {
143 \\ i += 10;
144 \\ } else {
145 \\ i = 2;
146 \\ }
147 \\ return i;
148 \\}
149 , "2\n");
150
151 case.addCompareOutput(
152 \\export fn _start() u32 {
153 \\ var i: u32 = 5;
154 \\ if (i < @as(u32, 4)) {
155 \\ i += 10;
156 \\ } else if(i == @as(u32, 5)) {
157 \\ i = 20;
158 \\ }
159 \\ return i;
160 \\}
161 , "20\n");
162
163 case.addCompareOutput(
164 \\export fn _start() u32 {
165 \\ var i: u32 = 11;
166 \\ if (i < @as(u32, 4)) {
167 \\ i += 10;
168 \\ } else {
169 \\ if (i > @as(u32, 10)) {
170 \\ i += 20;
171 \\ } else {
172 \\ i = 20;
173 \\ }
174 \\ }
175 \\ return i;
176 \\}
177 , "31\n");
178 }
179
180 {
181 var case = ctx.exe("wasm while loops", wasi);
182
183 case.addCompareOutput(
184 \\export fn _start() u32 {
185 \\ var i: u32 = 0;
186 \\ while(i < @as(u32, 5)){
187 \\ i += 1;
188 \\ }
189 \\
190 \\ return i;
191 \\}
192 , "5\n");
193
194 case.addCompareOutput(
195 \\export fn _start() u32 {
196 \\ var i: u32 = 0;
197 \\ while(i < @as(u32, 10)){
198 \\ var x: u32 = 1;
199 \\ i += x;
200 \\ }
201 \\ return i;
202 \\}
203 , "10\n");
204
205 case.addCompareOutput(
206 \\export fn _start() u32 {
207 \\ var i: u32 = 0;
208 \\ while(i < @as(u32, 10)){
209 \\ var x: u32 = 1;
210 \\ i += x;
211 \\ if (i == @as(u32, 5)) break;
212 \\ }
213 \\ return i;
214 \\}
215 , "5\n");
216 }
125217}