From 90d8544d4066a8aa42f1fe1d8cd052e8099d8152 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Mon, 22 Nov 2021 21:16:55 +0100 Subject: [PATCH 1/5] wasm: Add temporary stage2 entrypoint for wasm --- lib/std/start.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index d10b756160..39f0fd3525 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -30,6 +30,8 @@ comptime { } } else if (builtin.os.tag == .windows) { @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); + } else if (builtin.os.tag == .wasi) { + @export(wasmMain2, .{ .name = "_start" }); } else { if (!@hasDecl(root, "_start")) { @export(_start2, .{ .name = "_start" }); @@ -98,6 +100,11 @@ fn callMain2() noreturn { exit2(0); } +fn wasmMain2() u8 { + root.main(); + return 0; +} + fn wWinMainCRTStartup2() callconv(.C) noreturn { root.main(); exit2(0); From 9b5d61430fc7297b24d870adf42392ad113fa21b Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Mon, 22 Nov 2021 21:17:49 +0100 Subject: [PATCH 2/5] wasm: Implement slices --- src/arch/wasm/CodeGen.zig | 113 ++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 11 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 9c82f84b2c..3fac50ca7f 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1015,6 +1015,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .loop => self.airLoop(inst), .not => self.airNot(inst), .ret => self.airRet(inst), + .slice_len => self.airSliceLen(inst), + .slice_elem_val => self.airSliceElemVal(inst), .store => self.airStore(inst), .struct_field_ptr => self.airStructFieldPtr(inst), .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0), @@ -1145,13 +1147,16 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro // in memory try self.emitWValue(rhs); const tag_local = try self.allocLocal(tag_ty); - const payload_local = try self.allocLocal(payload_ty); - try self.addLabel(.local_set, payload_local.local); + if (payload_ty.hasCodeGenBits()) { + const payload_local = try self.allocLocal(payload_ty); + try self.addLabel(.local_set, payload_local.local); + try self.store(lhs, payload_local, payload_ty, payload_offset); + } try self.addLabel(.local_set, tag_local.local); try self.store(lhs, tag_local, tag_ty, 0); - return try self.store(lhs, payload_local, payload_ty, payload_offset); + return; }, .local => { // Load values from `rhs` stack position and store in `lhs` instead @@ -1197,9 +1202,16 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro try self.emitWValue(lhs); try self.emitWValue(rhs); const valtype = try self.typeToValtype(ty); + // check if we should pass by pointer or value based on ABI size + // TODO: Implement a way to get ABI values from a given type, + // that is portable across the backend, rather than copying logic. + const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8) + @intCast(u8, ty.abiSize(self.target)) + else + @as(u8, 4); const opcode = buildOpcode(.{ .valtype1 = valtype, - .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size + .width = abi_size * 8, // use bitsize instead of byte size .op = .store, }); @@ -1220,7 +1232,7 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const ty = self.air.getRefType(ty_op.ty); return switch (ty.zigTypeTag()) { - .Struct, .ErrorUnion, .Optional => operand, // pass as pointer + .Struct, .ErrorUnion, .Optional, .Pointer => operand, // pass as pointer else => switch (operand) { .local_with_offset => |with_offset| try self.load(operand, ty, with_offset.offset), else => try self.load(operand, ty, 0), @@ -1233,9 +1245,17 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue { try self.emitWValue(operand); // Build the opcode with the right bitsize const signedness: std.builtin.Signedness = if (ty.isUnsignedInt()) .unsigned else .signed; + // check if we should pass by pointer or value based on ABI size + // TODO: Implement a way to get ABI values from a given type, + // that is portable across the backend, rather than copying logic. + const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8) + @intCast(u8, ty.abiSize(self.target)) + else + @as(u8, 4); + const opcode = buildOpcode(.{ .valtype1 = try self.typeToValtype(ty), - .width = @intCast(u8, Type.abiSize(ty, self.target) * 8), // use bitsize instead of byte size + .width = abi_size * 8, // use bitsize instead of byte size .op = .load, .signedness = signedness, }); @@ -1399,14 +1419,19 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { const payload_val = pl.data; // no error, so write a '0' const try self.addImm32(0); - // after the error code, we emit the payload - try self.emitConstant(payload_val, payload_type); + + if (payload_type.hasCodeGenBits()) { + // after the error code, we emit the payload + try self.emitConstant(payload_val, payload_type); + } } else { // write the error val try self.emitConstant(val, error_type); - // no payload, so write a '0' const - try self.addImm32(0); + if (payload_type.hasCodeGenBits()) { + // no payload, so write a '0' const + try self.addImm32(0); + } } }, .Optional => { @@ -1867,8 +1892,10 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue const ty_op = self.air.instructions.items(.data)[inst].ty_op; const operand = self.resolveInst(ty_op.operand); const err_ty = self.air.typeOf(ty_op.operand); + const payload_ty = err_ty.errorUnionPayload(); + if (!payload_ty.hasCodeGenBits()) return WValue.none; const offset = @intCast(u32, err_ty.errorUnionSet().abiSize(self.target)); - return self.load(operand, err_ty.errorUnionPayload(), offset); + return try self.load(operand, payload_ty, offset); } fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { @@ -1961,3 +1988,67 @@ fn airWrapOptional(self: *Self, inst: Air.Inst.Index) InnerError!WValue { .offset = @intCast(u32, offset), } }; } + +fn airSliceLen(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue.none; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = self.resolveInst(ty_op.operand); + const pointer_width = self.target.cpu.arch.ptrBitWidth() / 8; + + // Get pointer to slice + try self.emitWValue(operand); + // length of slice is stored after the pointer of the slice + const extra_index = try self.addExtra(Mir.MemArg{ + .offset = pointer_width, + .alignment = pointer_width, + }); + try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } }); + + const result = try self.allocLocal(Type.initTag(.i32)); // pointer is always i32 + // store slice length in local + try self.addLabel(.local_set, result.local); + return result; +} + +fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue.none; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const slice_ty = self.air.typeOf(bin_op.lhs); + const slice = self.resolveInst(bin_op.lhs); + const index = self.resolveInst(bin_op.rhs); + const elem_ty = slice_ty.childType(); + const elem_size = elem_ty.abiSize(self.target); + + // load pointer onto stack + try self.emitWValue(slice); + + // calculate index into slice + try self.emitWValue(index); + try self.addImm32(@bitCast(i32, @intCast(u32, elem_size))); + try self.addTag(.i32_mul); + try self.addTag(.i32_add); + + const abi_size = if (elem_size < 8) + @intCast(u8, elem_size) + else + @as(u8, 4); // elements larger than 8 bytes will be passed by pointer + + const extra_index = try self.addExtra(Mir.MemArg{ + .offset = 0, + .alignment = elem_ty.abiAlignment(self.target), + }); + const signedness: std.builtin.Signedness = if (elem_ty.isUnsignedInt()) .unsigned else .signed; + const opcode = buildOpcode(.{ + .valtype1 = try self.typeToValtype(elem_ty), + .width = abi_size * 8, + .op = .load, + .signedness = signedness, + }); + try self.addInst(.{ .tag = Mir.Inst.Tag.fromOpcode(opcode), .data = .{ .payload = extra_index } }); + + const result = try self.allocLocal(elem_ty); + try self.addLabel(.local_set, result.local); + return result; +} From 7226ad2670f267b4d90b84d0e104fbb1fa41fe49 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 28 Nov 2021 12:49:04 +0100 Subject: [PATCH 3/5] wasm-link: Implement indirect function table The function table contains all function pointers that are called by using call_indirect. During codegen, we create a relocation where the linker will resolve the correct index into the table and stores this value within the data section at the location of the pointer. --- src/arch/wasm/CodeGen.zig | 30 +++++++++++++-- src/arch/wasm/Emit.zig | 8 ++++ src/arch/wasm/Mir.zig | 5 +++ src/link/Wasm.zig | 80 +++++++++++++++++++++++++++++++-------- src/link/Wasm/Atom.zig | 2 +- 5 files changed, 105 insertions(+), 20 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 3fac50ca7f..cbda80aee3 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1065,9 +1065,16 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const extra = self.air.extraData(Air.Call, pl_op.payload); const args = self.air.extra[extra.end..][0..extra.data.args_len]; + const ty = self.air.typeOf(pl_op.operand); - const target: *Decl = blk: { - const func_val = self.air.value(pl_op.operand).?; + const fn_ty = switch (ty.zigTypeTag()) { + .Fn => ty, + .Pointer => ty.childType(), + else => unreachable, + }; + + const target: ?*Decl = blk: { + const func_val = self.air.value(pl_op.operand) orelse break :blk null; if (func_val.castTag(.function)) |func| { break :blk func.data.owner_decl; @@ -1082,9 +1089,24 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { try self.emitWValue(arg_val); } - try self.addLabel(.call, target.link.wasm.sym_index); + if (target) |direct| { + try self.addLabel(.call, direct.link.wasm.sym_index); + } else { + // in this case we call a function pointer + // so load its value onto the stack + std.debug.assert(ty.zigTypeTag() == .Pointer); + const operand = self.resolveInst(pl_op.operand); + const result = try self.load(operand, fn_ty, operand.local_with_offset.offset); + try self.addLabel(.local_get, result.local); - const ret_ty = target.ty.fnReturnType(); + var fn_type = try self.genFunctype(fn_ty); + defer fn_type.deinit(self.gpa); + + const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type); + try self.addLabel(.call_indirect, fn_type_index); + } + + const ret_ty = fn_ty.fnReturnType(); switch (ret_ty.zigTypeTag()) { .Void, .NoReturn => return WValue.none, else => { diff --git a/src/arch/wasm/Emit.zig b/src/arch/wasm/Emit.zig index d01f319b91..c4368ab943 100644 --- a/src/arch/wasm/Emit.zig +++ b/src/arch/wasm/Emit.zig @@ -47,6 +47,7 @@ pub fn emitMir(emit: *Emit) InnerError!void { // relocatables .call => try emit.emitCall(inst), + .call_indirect => try emit.emitCallIndirect(inst), .global_get => try emit.emitGlobal(tag, inst), .global_set => try emit.emitGlobal(tag, inst), .memory_address => try emit.emitMemAddress(inst), @@ -276,6 +277,13 @@ fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void { }); } +fn emitCallIndirect(emit: *Emit, inst: Mir.Inst.Index) !void { + const label = emit.mir.instructions.items(.data)[inst].label; + try emit.code.append(std.wasm.opcode(.call_indirect)); + try leb128.writeULEB128(emit.code.writer(), @as(u32, 0)); // TODO: Emit relocation for table index + try leb128.writeULEB128(emit.code.writer(), label); +} + fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void { const symbol_index = emit.mir.instructions.items(.data)[inst].label; try emit.code.append(std.wasm.opcode(.i32_const)); diff --git a/src/arch/wasm/Mir.zig b/src/arch/wasm/Mir.zig index 97d8875984..5891893f33 100644 --- a/src/arch/wasm/Mir.zig +++ b/src/arch/wasm/Mir.zig @@ -69,6 +69,11 @@ pub const Inst = struct { /// /// Uses `label` call = 0x10, + /// Calls a function pointer by its function signature + /// and index into the function table. + /// + /// Uses `label` + call_indirect = 0x11, /// Loads a local at given index onto the stack. /// /// Uses `label` diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 9490634dc1..8933bdef9f 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -79,7 +79,9 @@ memories: wasm.Memory = .{ .limits = .{ .min = 0, .max = null } }, /// Indirect function table, used to call function pointers /// When this is non-zero, we must emit a table entry, /// as well as an 'elements' section. -function_table: std.ArrayListUnmanaged(Symbol) = .{}, +/// +/// Note: Key is symbol index, value represents the index into the table +function_table: std.AutoHashMapUnmanaged(u32, u32) = .{}, pub const Segment = struct { alignment: u32, @@ -276,7 +278,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { defer codegen.deinit(); // generate the 'code' section for the function declaration - const result = codegen.gen(decl.ty, decl.val) catch |err| switch (err) { + const result = codegen.genDecl(decl.ty, decl.val) catch |err| switch (err) { error.CodegenFail => { decl.analysis = .codegen_failure; try module.failed_decls.put(module.gpa, decl, codegen.err_msg); @@ -334,6 +336,25 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { else => unreachable, } } + + // maybe remove from function table if needed + if (decl.ty.zigTypeTag() == .Fn) { + _ = self.function_table.remove(atom.sym_index); + } +} + +/// Appends a new entry to the indirect function table +pub fn addTableFunction(self: *Wasm, symbol_index: u32) !void { + const index = @intCast(u32, self.function_table.count()); + try self.function_table.put(self.base.allocator, symbol_index, index); +} + +fn mapFunctionTable(self: *Wasm) void { + var it = self.function_table.valueIterator(); + var index: u32 = 0; + while (it.next()) |value_ptr| : (index += 1) { + value_ptr.* = index; + } } fn addOrUpdateImport(self: *Wasm, decl: *Module.Decl) !void { @@ -583,6 +604,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { try self.setupMemory(); try self.allocateAtoms(); + self.mapFunctionTable(); const file = self.base.file.?; const header_size = 5 + 1; @@ -662,6 +684,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { ); } + if (self.function_table.count() > 0) { + const header_offset = try reserveVecSectionHeader(file); + const writer = file.writer(); + + try leb.writeULEB128(writer, wasm.reftype(.funcref)); + try emitLimits(writer, .{ .min = 1, .max = null }); + + try writeVecSectionHeader( + file, + header_offset, + .table, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + @as(u32, 1), + ); + } + // Memory section if (!self.base.options.import_memory) { const header_offset = try reserveVecSectionHeader(file); @@ -743,6 +781,31 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { ); } + // element section (function table) + if (self.function_table.count() > 0) { + const header_offset = try reserveVecSectionHeader(file); + const writer = file.writer(); + + var flags: u32 = 0x2; // Yes we have a table + try leb.writeULEB128(writer, flags); + try leb.writeULEB128(writer, @as(u32, 0)); // index of that table. TODO: Store synthetic symbols + try emitInit(writer, .{ .i32_const = 0 }); + try leb.writeULEB128(writer, @as(u8, 0)); + try leb.writeULEB128(writer, @intCast(u32, self.function_table.count())); + var symbol_it = self.function_table.keyIterator(); + while (symbol_it.next()) |symbol_index_ptr| { + try leb.writeULEB128(writer, self.symbols.items[symbol_index_ptr.*].index); + } + + try writeVecSectionHeader( + file, + header_offset, + .element, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + @as(u32, 1), + ); + } + // Code section if (self.code_section_index) |code_index| { const header_offset = try reserveVecSectionHeader(file); @@ -1233,16 +1296,3 @@ pub fn putOrGetFuncType(self: *Wasm, func_type: wasm.Type) !u32 { }); return index; } - -/// From a given index and an `ExternalKind`, finds the corresponding Import. -/// This is due to indexes for imports being unique per type, rather than across all imports. -fn findImport(self: Wasm, index: u32, external_type: wasm.ExternalKind) ?*wasm.Import { - var current_index: u32 = 0; - for (self.imports.items) |*import| { - if (import.kind == external_type) { - if (current_index == index) return import; - current_index += 1; - } - } - return null; -} diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index ec336718cc..7f49e069b1 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -129,7 +129,7 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 { .R_WASM_TABLE_INDEX_I64, .R_WASM_TABLE_INDEX_SLEB, .R_WASM_TABLE_INDEX_SLEB64, - => return error.TodoImplementTableIndex, // find table index from a function symbol + => return wasm_bin.function_table.get(relocation.index) orelse 0, .R_WASM_TYPE_INDEX_LEB => wasm_bin.functions.items[symbol.index].type_index, .R_WASM_GLOBAL_INDEX_I32, .R_WASM_GLOBAL_INDEX_LEB, From dd49eca34274cd2396cbe06a199fe8db9e8faf79 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 28 Nov 2021 20:25:33 +0100 Subject: [PATCH 4/5] wasm: Implement 'zig test' - This implements the required codegen for decl types such as pointers, arrays, structs and more. - Wasm's start function can now use both a 'u8' and 'void' as return type. This will help us with writing tests using the stage2 testing backend. (Until all tests of behavioural tests pass). - Now correctly generates relocations for function pointers. - Also implements unwrapping error union error, as well as return pointers. --- lib/std/start.zig | 15 ++- src/arch/wasm/CodeGen.zig | 212 +++++++++++++++++++++++++++++++++----- src/arch/wasm/Emit.zig | 2 +- src/link/Wasm/Atom.zig | 6 +- 4 files changed, 203 insertions(+), 32 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 39f0fd3525..ed4b4fc3c8 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -101,8 +101,19 @@ fn callMain2() noreturn { } fn wasmMain2() u8 { - root.main(); - return 0; + switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) { + .Void => { + root.main(); + return 0; + }, + .Int => |info| { + if (info.bits != 8 or info.signedness == .signed) { + @compileError(bad_main_ret); + } + return root.main(); + }, + else => @compileError("Bad return type main"), + } } fn wWinMainCRTStartup2() callconv(.C) noreturn { diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index cbda80aee3..9e4315ead9 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -692,6 +692,7 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype { .Struct, .ErrorUnion, .Optional, + .Fn, => wasm.Valtype.i32, else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}), }; @@ -809,23 +810,52 @@ pub fn genFunc(self: *Self) InnerError!Result { } /// Generates the wasm bytecode for the declaration belonging to `Context` -pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result { +pub fn genDecl(self: *Self, ty: Type, val: Value) InnerError!Result { + if (val.isUndef()) { + try self.code.appendNTimes(0xaa, ty.abiSize(self.target)); + return Result.appended; + } switch (ty.zigTypeTag()) { .Fn => { - if (val.tag() == .extern_fn) { - var func_type = try self.genFunctype(self.decl.ty); - defer func_type.deinit(self.gpa); - self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type); - return Result.appended; // don't need code body for extern functions - } - return self.fail("TODO implement wasm codegen for function pointers", .{}); + const fn_decl = switch (val.tag()) { + .extern_fn => val.castTag(.extern_fn).?.data, + .function => val.castTag(.function).?.data.owner_decl, + else => unreachable, + }; + return try self.lowerDeclRef(fn_decl); }, - .Array => { - if (val.castTag(.bytes)) |payload| { + .Optional => { + var opt_buf: Type.Payload.ElemType = undefined; + const payload_type = ty.optionalChild(&opt_buf); + if (ty.isPtrLikeOptional()) { + if (val.castTag(.opt_payload)) |payload| { + return try self.genDecl(payload_type, payload.data); + } else if (!val.isNull()) { + return try self.genDecl(payload_type, val); + } else { + try self.code.appendNTimes(0, ty.abiSize(self.target)); + return Result.appended; + } + } + // `null-tag` byte + try self.code.appendNTimes(@boolToInt(!val.isNull()), 4); + const pl_result = try self.genDecl( + payload_type, + if (val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef), + ); + switch (pl_result) { + .appended => {}, + .externally_managed => |payload| try self.code.appendSlice(payload), + } + return Result.appended; + }, + .Array => switch (val.tag()) { + .bytes => { + const payload = val.castTag(.bytes).?; if (ty.sentinel()) |sentinel| { try self.code.appendSlice(payload.data); - switch (try self.gen(ty.childType(), sentinel)) { + switch (try self.genDecl(ty.childType(), sentinel)) { .appended => return Result.appended, .externally_managed => |data| { try self.code.appendSlice(data); @@ -834,16 +864,33 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result { } } return Result{ .externally_managed = payload.data }; - } else return self.fail("TODO implement gen for more kinds of arrays", .{}); + }, + .array => { + const elem_vals = val.castTag(.array).?.data; + const elem_ty = ty.elemType(); + for (elem_vals) |elem_val| { + switch (try self.genDecl(elem_ty, elem_val)) { + .appended => {}, + .externally_managed => |data| { + try self.code.appendSlice(data); + }, + } + } + return Result.appended; + }, + else => return self.fail("TODO implement genDecl for array type value: {s}", .{@tagName(val.tag())}), }, .Int => { const info = ty.intInfo(self.target); - if (info.bits == 8 and info.signedness == .unsigned) { - const int_byte = val.toUnsignedInt(); - try self.code.append(@intCast(u8, int_byte)); - return Result.appended; - } - return self.fail("TODO: Implement codegen for int type: '{}'", .{ty}); + const abi_size = ty.abiSize(self.target); + // todo: Implement integer sizes larger than 64bits + if (info.bits > 64) return self.fail("TODO: Implement genDecl for integer bit size: {d}", .{info.bits}); + var buf: [8]u8 = undefined; + if (info.signedness == .unsigned) { + std.mem.writeIntLittle(u64, &buf, val.toUnsignedInt()); + } else std.mem.writeIntLittle(i64, &buf, val.toSignedInt()); + try self.code.appendSlice(buf[0..abi_size]); + return Result.appended; }, .Enum => { try self.emitConstant(val, ty); @@ -855,15 +902,83 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result { return Result.appended; }, .Struct => { - // TODO write the fields for real - const abi_size = try std.math.cast(usize, ty.abiSize(self.target)); + const field_vals = val.castTag(.@"struct").?.data; + for (field_vals) |field_val, index| { + const field_ty = ty.structFieldType(index); + if (!field_ty.hasCodeGenBits()) continue; + + switch (try self.genDecl(field_ty, field_val)) { + .appended => {}, + .externally_managed => |payload| try self.code.appendSlice(payload), + } + } + return Result.appended; + }, + .Union => { + // TODO: Implement Union declarations + const abi_size = ty.abiSize(self.target); try self.code.writer().writeByteNTimes(0xaa, abi_size); - return Result{ .appended = {} }; + return Result.appended; + }, + .Pointer => switch (val.tag()) { + .variable => { + const decl = val.castTag(.variable).?.data.owner_decl; + return try self.lowerDeclRef(decl); + }, + .decl_ref => { + const decl = val.castTag(.decl_ref).?.data; + return try self.lowerDeclRef(decl); + }, + .slice => { + const slice = val.castTag(.slice).?.data; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; + const ptr_ty = ty.slicePtrFieldType(&buf); + switch (try self.genDecl(ptr_ty, slice.ptr)) { + .externally_managed => |data| try self.code.appendSlice(data), + .appended => {}, + } + switch (try self.genDecl(Type.usize, slice.len)) { + .externally_managed => |data| try self.code.appendSlice(data), + .appended => {}, + } + return Result.appended; + }, + else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}), }, else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}), } } +fn lowerDeclRef(self: *Self, decl: *Module.Decl) InnerError!Result { + decl.alive = true; + + const offset = @intCast(u32, self.code.items.len); + const atom = &self.decl.link.wasm; + const target_sym_index = decl.link.wasm.sym_index; + + if (decl.ty.zigTypeTag() == .Fn) { + // We found a function pointer, so add it to our table, + // as function pointers are not allowed to be stored inside the data section, + // but rather in a function table which are called by index + try self.bin_file.addTableFunction(target_sym_index); + try atom.relocs.append(self.gpa, .{ + .index = target_sym_index, + .offset = offset, + .relocation_type = .R_WASM_TABLE_INDEX_I32, + }); + } else { + try atom.relocs.append(self.gpa, .{ + .index = target_sym_index, + .offset = offset, + .relocation_type = .R_WASM_MEMORY_ADDR_I32, + }); + } + const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8; + try self.code.appendNTimes(0xaa, ptr_width); + + return Result.appended; +} + const CallWValues = struct { args: []WValue, return_value: WValue, @@ -1015,6 +1130,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .loop => self.airLoop(inst), .not => self.airNot(inst), .ret => self.airRet(inst), + .ret_ptr => self.airRetPtr(inst), + .ret_load => self.airRetLoad(inst), .slice_len => self.airSliceLen(inst), .slice_elem_val => self.airSliceElemVal(inst), .store => self.airStore(inst), @@ -1029,6 +1146,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .wrap_optional => self.airWrapOptional(inst), .unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst), + .unwrap_errunion_err => self.airUnwrapErrUnionError(inst), .wrap_errunion_payload => self.airWrapErrUnionPayload(inst), .optional_payload => self.airOptionalPayload(inst), @@ -1061,6 +1179,34 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue { return .none; } +fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + const child_type = self.air.typeOfIndex(inst).childType(); + + // Initialize the stack + if (self.initial_stack_value == .none) { + try self.initializeStack(); + } + + const abi_size = child_type.abiSize(self.target); + if (abi_size == 0) return WValue{ .none = {} }; + + // local, containing the offset to the stack position + const local = try self.allocLocal(Type.initTag(.i32)); // always pointer therefore i32 + try self.moveStack(@intCast(u32, abi_size), local.local); + + return local; +} + +fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = self.resolveInst(un_op); + const result = try self.load(operand, self.air.typeOf(un_op), 0); + try self.addLabel(.local_get, result.local); + try self.restoreStackPointer(); + try self.addTag(.@"return"); + return .none; +} + fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const extra = self.air.extraData(Air.Call, pl_op.payload); @@ -1096,6 +1242,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue { // so load its value onto the stack std.debug.assert(ty.zigTypeTag() == .Pointer); const operand = self.resolveInst(pl_op.operand); + try self.emitWValue(operand); const result = try self.load(operand, fn_ty, operand.local_with_offset.offset); try self.addLabel(.local_get, result.local); @@ -1229,6 +1376,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro // that is portable across the backend, rather than copying logic. const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8) @intCast(u8, ty.abiSize(self.target)) + else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum) + @intCast(u8, ty.abiSize(self.target)) else @as(u8, 4); const opcode = buildOpcode(.{ @@ -1272,6 +1421,8 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue { // that is portable across the backend, rather than copying logic. const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8) @intCast(u8, ty.abiSize(self.target)) + else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum) + @intCast(u8, ty.abiSize(self.target)) else @as(u8, 4); @@ -1920,6 +2071,15 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue return try self.load(operand, payload_ty, offset); } +fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index) InnerError!WValue { + if (self.liveness.isUnused(inst)) return WValue.none; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = self.resolveInst(ty_op.operand); + const err_ty = self.air.typeOf(ty_op.operand); + return try self.load(operand, err_ty.errorUnionSet(), 0); +} + fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const ty_op = self.air.instructions.items(.data)[inst].ty_op; _ = ty_op; @@ -1935,18 +2095,20 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue { const op_bits = ref_info.bits; const wanted_bits = ty.intInfo(self.target).bits; - try self.emitWValue(operand); if (op_bits > 32 and wanted_bits <= 32) { + try self.emitWValue(operand); try self.addTag(.i32_wrap_i64); } else if (op_bits <= 32 and wanted_bits > 32) { + try self.emitWValue(operand); try self.addTag(switch (ref_info.signedness) { .signed => .i64_extend_i32_s, .unsigned => .i64_extend_i32_u, }); - } + } else return operand; - // other cases are no-op - return .none; + const result = try self.allocLocal(ty); + try self.addLabel(.local_set, result.local); + return result; } fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue { diff --git a/src/arch/wasm/Emit.zig b/src/arch/wasm/Emit.zig index c4368ab943..e912e05add 100644 --- a/src/arch/wasm/Emit.zig +++ b/src/arch/wasm/Emit.zig @@ -257,7 +257,7 @@ fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void { try emit.code.append(@enumToInt(tag)); // wasm encodes alignment as power of 2, rather than natural alignment - const encoded_alignment = mem_arg.alignment >> 1; + const encoded_alignment = @ctz(u32, mem_arg.alignment); try leb128.writeULEB128(emit.code.writer(), encoded_alignment); try leb128.writeULEB128(emit.code.writer(), mem_arg.offset); } diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index 7f49e069b1..0055955bde 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -83,7 +83,7 @@ pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void { for (self.relocs.items) |reloc| { const value = try relocationValue(reloc, wasm_bin); - log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}\n", .{ + log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{ wasm_bin.symbols.items[reloc.index].name, symbol.name, reloc.offset, @@ -152,9 +152,7 @@ fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 { target_atom = target_atom.next orelse break; } const segment = wasm_bin.segments.items[atom_index]; - const base = wasm_bin.base.options.global_base orelse 1024; - const offset = target_atom.offset + segment.offset; - break :blk offset + base + (relocation.addend orelse 0); + break :blk target_atom.offset + segment.offset + (relocation.addend orelse 0); }, .R_WASM_EVENT_INDEX_LEB => symbol.index, .R_WASM_SECTION_OFFSET_I32, From adf059f272dfd3c1652bce774c0b6c204d5d6b8b Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 28 Nov 2021 20:31:12 +0100 Subject: [PATCH 5/5] wasm: Update wasm stage2 test backend to use 'main' --- lib/std/start.zig | 2 +- src/arch/wasm/CodeGen.zig | 12 +- test/cases.zig | 2 +- test/stage2/darwin.zig | 2 +- test/stage2/wasm.zig | 395 ++++++++++++++++++++------------------ 5 files changed, 219 insertions(+), 194 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index ed4b4fc3c8..1d3cad93d2 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -30,7 +30,7 @@ comptime { } } else if (builtin.os.tag == .windows) { @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); - } else if (builtin.os.tag == .wasi) { + } else if (builtin.os.tag == .wasi and @hasDecl(root, "main")) { @export(wasmMain2, .{ .name = "_start" }); } else { if (!@hasDecl(root, "_start")) { diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 9e4315ead9..55b0aff81f 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -812,7 +812,7 @@ pub fn genFunc(self: *Self) InnerError!Result { /// Generates the wasm bytecode for the declaration belonging to `Context` pub fn genDecl(self: *Self, ty: Type, val: Value) InnerError!Result { if (val.isUndef()) { - try self.code.appendNTimes(0xaa, ty.abiSize(self.target)); + try self.code.appendNTimes(0xaa, @intCast(usize, ty.abiSize(self.target))); return Result.appended; } switch (ty.zigTypeTag()) { @@ -833,7 +833,7 @@ pub fn genDecl(self: *Self, ty: Type, val: Value) InnerError!Result { } else if (!val.isNull()) { return try self.genDecl(payload_type, val); } else { - try self.code.appendNTimes(0, ty.abiSize(self.target)); + try self.code.appendNTimes(0, @intCast(usize, ty.abiSize(self.target))); return Result.appended; } } @@ -882,7 +882,7 @@ pub fn genDecl(self: *Self, ty: Type, val: Value) InnerError!Result { }, .Int => { const info = ty.intInfo(self.target); - const abi_size = ty.abiSize(self.target); + const abi_size = @intCast(usize, ty.abiSize(self.target)); // todo: Implement integer sizes larger than 64bits if (info.bits > 64) return self.fail("TODO: Implement genDecl for integer bit size: {d}", .{info.bits}); var buf: [8]u8 = undefined; @@ -916,8 +916,8 @@ pub fn genDecl(self: *Self, ty: Type, val: Value) InnerError!Result { }, .Union => { // TODO: Implement Union declarations - const abi_size = ty.abiSize(self.target); - try self.code.writer().writeByteNTimes(0xaa, abi_size); + const abi_size = @intCast(usize, ty.abiSize(self.target)); + try self.code.appendNTimes(0xaa, abi_size); return Result.appended; }, .Pointer => switch (val.tag()) { @@ -973,7 +973,7 @@ fn lowerDeclRef(self: *Self, decl: *Module.Decl) InnerError!Result { .relocation_type = .R_WASM_MEMORY_ADDR_I32, }); } - const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8; + const ptr_width = @intCast(usize, self.target.cpu.arch.ptrBitWidth() / 8); try self.code.appendNTimes(0xaa, ptr_width); return Result.appended; diff --git a/test/cases.zig b/test/cases.zig index eb0b8fb8b9..4a0810ef8a 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -26,7 +26,7 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("hello world with updates", linux_x64); case.addError("", &[_][]const u8{ - ":97:9: error: struct 'tmp.tmp' has no member named 'main'", + ":99:9: error: struct 'tmp.tmp' has no member named 'main'", }); // Incorrect return type diff --git a/test/stage2/darwin.zig b/test/stage2/darwin.zig index ab4f3923ae..3976dda08f 100644 --- a/test/stage2/darwin.zig +++ b/test/stage2/darwin.zig @@ -14,7 +14,7 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("darwin hello world with updates", target); case.addError("", &[_][]const u8{ - ":97:9: error: struct 'tmp.tmp' has no member named 'main'", + ":99:9: error: struct 'tmp.tmp' has no member named 'main'", }); // Incorrect return type diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig index b28834e9b7..20ff65e0a7 100644 --- a/test/stage2/wasm.zig +++ b/test/stage2/wasm.zig @@ -11,7 +11,7 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm function calls", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ foo(); \\ bar(); \\ return 42; @@ -26,7 +26,7 @@ pub fn addCases(ctx: *TestContext) !void { ); case.addCompareOutput( - \\pub export fn _start() i64 { + \\pub fn main() u8 { \\ bar(); \\ foo(); \\ foo(); @@ -44,10 +44,10 @@ pub fn addCases(ctx: *TestContext) !void { ); case.addCompareOutput( - \\pub export fn _start() f32 { + \\pub fn main() void { \\ bar(); \\ foo(); - \\ return 42.0; + \\ return; \\} \\fn foo() void { \\ bar(); @@ -56,15 +56,15 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\fn bar() void {} , - "42\n", + "0\n", ); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ foo(10, 20); \\ return 5; \\} - \\fn foo(x: u32, y: u32) void { _ = x; _ = y; } + \\fn foo(x: u8, y: u8) void { _ = x; _ = y; } , "5\n"); } @@ -72,10 +72,10 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm locals", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ var y: f32 = 42.0; - \\ var x: u32 = 10; + \\ var x: u8 = 10; \\ if (false) { \\ y; \\ x; @@ -85,18 +85,18 @@ pub fn addCases(ctx: *TestContext) !void { , "5\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ var y: f32 = 42.0; \\ _ = y; - \\ var x: u32 = 10; + \\ var x: u8 = 10; \\ foo(i, x); \\ i = x; \\ return i; \\} - \\fn foo(x: u32, y: u32) void { + \\fn foo(x: u8, y: u8) void { \\ _ = y; - \\ var i: u32 = 10; + \\ var i: u8 = 10; \\ i = x; \\} , "10\n"); @@ -106,228 +106,246 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm binary operands", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ i += 20; \\ return i; \\} , "25\n"); case.addCompareOutput( - \\pub export fn _start() i32 { + \\pub fn main() void { \\ var i: i32 = 2147483647; - \\ return i +% 1; - \\} - , "-2147483648\n"); - - case.addCompareOutput( - \\pub export fn _start() i32 { - \\ var i: i4 = 7; - \\ return i +% 1; + \\ if (i +% 1 != -2147483648) unreachable; + \\ return; \\} , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() void { + \\ var i: i4 = 7; + \\ if (i +% 1 != 0) unreachable; + \\ return; + \\} + , "0\n"); + + case.addCompareOutput( + \\pub fn main() u8 { \\ var i: u8 = 255; \\ return i +% 1; \\} , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ i += 20; - \\ var result: u32 = foo(i, 10); + \\ var result: u8 = foo(i, 10); \\ return result; \\} - \\fn foo(x: u32, y: u32) u32 { + \\fn foo(x: u8, y: u8) u8 { \\ return x + y; \\} , "35\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 20; + \\pub fn main() u8 { + \\ var i: u8 = 20; \\ i -= 5; \\ return i; \\} , "15\n"); case.addCompareOutput( - \\pub export fn _start() i32 { + \\pub fn main() void { \\ var i: i32 = -2147483648; - \\ return i -% 1; + \\ if (i -% 1 != 2147483647) unreachable; + \\ return; \\} - , "2147483647\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() i32 { + \\pub fn main() void { \\ var i: i7 = -64; - \\ return i -% 1; + \\ if (i -% 1 != 63) unreachable; + \\ return; \\} - , "63\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var i: u4 = 0; \\ return i -% 1; \\} , "15\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ i -= 3; - \\ var result: u32 = foo(i, 10); + \\ var result: u8 = foo(i, 10); \\ return result; \\} - \\fn foo(x: u32, y: u32) u32 { + \\fn foo(x: u8, y: u8) u8 { \\ return y - x; \\} , "8\n"); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() void { \\ var i: u32 = 5; \\ i *= 7; \\ var result: u32 = foo(i, 10); - \\ return result; + \\ if (result != 350) unreachable; + \\ return; \\} \\fn foo(x: u32, y: u32) u32 { \\ return x * y; \\} - , "350\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() i32 { + \\pub fn main() void { \\ var i: i32 = 2147483647; - \\ return i *% 2; + \\ const result = i *% 2; + \\ if (result != -2) unreachable; + \\ return; \\} - , "-2\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() void { \\ var i: u3 = 3; - \\ return i *% 3; + \\ if (i *% 3 != 1) unreachable; + \\ return; \\} - , "1\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() i32 { + \\pub fn main() void { \\ var i: i4 = 3; - \\ return i *% 3; + \\ if (i *% 3 != 1) unreachable; + \\ return; \\} - , "1\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() void { \\ var i: u32 = 352; \\ i /= 7; // i = 50 \\ var result: u32 = foo(i, 7); - \\ return result; + \\ if (result != 7) unreachable; + \\ return; \\} \\fn foo(x: u32, y: u32) u32 { \\ return x / y; \\} - , "7\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ i &= 6; \\ return i; \\} , "4\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ i |= 6; \\ return i; \\} , "7\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; + \\pub fn main() u8 { + \\ var i: u8 = 5; \\ i ^= 6; \\ return i; \\} , "3\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = false; \\ b = b or false; - \\ return b; + \\ if (b) unreachable; + \\ return; \\} , "0\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = true; \\ b = b or false; - \\ return b; + \\ if (!b) unreachable; + \\ return; \\} - , "1\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = false; \\ b = b or true; - \\ return b; + \\ if (!b) unreachable; + \\ return; \\} - , "1\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = true; \\ b = b or true; - \\ return b; + \\ if (!b) unreachable; + \\ return; \\} - , "1\n"); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = false; \\ b = b and false; - \\ return b; + \\ if (b) unreachable; + \\ return; \\} , "0\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = true; \\ b = b and false; - \\ return b; + \\ if (b) unreachable; + \\ return; \\} , "0\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = false; \\ b = b and true; - \\ return b; + \\ if (b) unreachable; + \\ return; \\} , "0\n"); case.addCompareOutput( - \\pub export fn _start() bool { + \\pub fn main() void { \\ var b: bool = true; \\ b = b and true; - \\ return b; + \\ if (!b) unreachable; + \\ return; \\} - , "1\n"); + , "0\n"); } { var case = ctx.exe("wasm conditions", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; - \\ if (i > @as(u32, 4)) { + \\pub fn main() u8 { + \\ var i: u8 = 5; + \\ if (i > @as(u8, 4)) { \\ i += 10; \\ } \\ return i; @@ -335,9 +353,9 @@ pub fn addCases(ctx: *TestContext) !void { , "15\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; - \\ if (i < @as(u32, 4)) { + \\pub fn main() u8 { + \\ var i: u8 = 5; + \\ if (i < @as(u8, 4)) { \\ i += 10; \\ } else { \\ i = 2; @@ -347,11 +365,11 @@ pub fn addCases(ctx: *TestContext) !void { , "2\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 5; - \\ if (i < @as(u32, 4)) { + \\pub fn main() u8 { + \\ var i: u8 = 5; + \\ if (i < @as(u8, 4)) { \\ i += 10; - \\ } else if(i == @as(u32, 5)) { + \\ } else if(i == @as(u8, 5)) { \\ i = 20; \\ } \\ return i; @@ -359,12 +377,12 @@ pub fn addCases(ctx: *TestContext) !void { , "20\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 11; - \\ if (i < @as(u32, 4)) { + \\pub fn main() u8 { + \\ var i: u8 = 11; + \\ if (i < @as(u8, 4)) { \\ i += 10; \\ } else { - \\ if (i > @as(u32, 10)) { + \\ if (i > @as(u8, 10)) { \\ i += 20; \\ } else { \\ i = 20; @@ -375,7 +393,7 @@ pub fn addCases(ctx: *TestContext) !void { , "31\n"); case.addCompareOutput( - \\pub export fn _start() void { + \\pub fn main() void { \\ assert(foo(true) != @as(i32, 30)); \\} \\ @@ -387,10 +405,10 @@ pub fn addCases(ctx: *TestContext) !void { \\ const x = if(ok) @as(i32, 20) else @as(i32, 10); \\ return x; \\} - , ""); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() void { + \\pub fn main() void { \\ assert(foo(false) == @as(i32, 20)); \\ assert(foo(true) == @as(i32, 30)); \\} @@ -407,16 +425,16 @@ pub fn addCases(ctx: *TestContext) !void { \\ }; \\ return val + 10; \\} - , ""); + , "0\n"); } { var case = ctx.exe("wasm while loops", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 0; - \\ while(i < @as(u32, 5)){ + \\pub fn main() u8 { + \\ var i: u8 = 0; + \\ while(i < @as(u8, 5)){ \\ i += 1; \\ } \\ @@ -425,10 +443,10 @@ pub fn addCases(ctx: *TestContext) !void { , "5\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 0; - \\ while(i < @as(u32, 10)){ - \\ var x: u32 = 1; + \\pub fn main() u8 { + \\ var i: u8 = 0; + \\ while(i < @as(u8, 10)){ + \\ var x: u8 = 1; \\ i += x; \\ } \\ return i; @@ -436,12 +454,12 @@ pub fn addCases(ctx: *TestContext) !void { , "10\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var i: u32 = 0; - \\ while(i < @as(u32, 10)){ - \\ var x: u32 = 1; + \\pub fn main() u8 { + \\ var i: u8 = 0; + \\ while(i < @as(u8, 10)){ + \\ var x: u8 = 1; \\ i += x; - \\ if (i == @as(u32, 5)) break; + \\ if (i == @as(u8, 5)) break; \\ } \\ return i; \\} @@ -454,7 +472,7 @@ pub fn addCases(ctx: *TestContext) !void { case.addCompareOutput( \\const Number = enum { One, Two, Three }; \\ - \\pub export fn _start() i32 { + \\pub fn main() void { \\ var number1 = Number.One; \\ var number2: Number = .Two; \\ if (false) { @@ -462,47 +480,52 @@ pub fn addCases(ctx: *TestContext) !void { \\ number2; \\ } \\ const number3 = @intToEnum(Number, 2); - \\ - \\ return @enumToInt(number3); + \\ if (@enumToInt(number3) != 2) { + \\ unreachable; + \\ } + \\ return; \\} - , "2\n"); + , "0\n"); case.addCompareOutput( \\const Number = enum { One, Two, Three }; \\ - \\pub export fn _start() i32 { + \\pub fn main() void { \\ var number1 = Number.One; \\ var number2: Number = .Two; \\ const number3 = @intToEnum(Number, 2); - \\ if (number1 == number2) return 1; - \\ if (number2 == number3) return 1; - \\ if (@enumToInt(number1) != 0) return 1; - \\ if (@enumToInt(number2) != 1) return 1; - \\ if (@enumToInt(number3) != 2) return 1; + \\ assert(number1 != number2); + \\ assert(number2 != number3); + \\ assert(@enumToInt(number1) == 0); + \\ assert(@enumToInt(number2) == 1); + \\ assert(@enumToInt(number3) == 2); \\ var x: Number = .Two; - \\ if (number2 != x) return 1; + \\ assert(number2 == x); \\ - \\ return @enumToInt(number3); + \\ return; \\} - , "2\n"); + \\fn assert(val: bool) void { + \\ if(!val) unreachable; + \\} + , "0\n"); } { var case = ctx.exe("wasm structs", wasi); case.addCompareOutput( - \\const Example = struct { x: u32 }; + \\const Example = struct { x: u8 }; \\ - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var example: Example = .{ .x = 5 }; \\ return example.x; \\} , "5\n"); case.addCompareOutput( - \\const Example = struct { x: u32 }; + \\const Example = struct { x: u8 }; \\ - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var example: Example = .{ .x = 5 }; \\ example.x = 10; \\ return example.x; @@ -510,18 +533,18 @@ pub fn addCases(ctx: *TestContext) !void { , "10\n"); case.addCompareOutput( - \\const Example = struct { x: u32, y: u32 }; + \\const Example = struct { x: u8, y: u8 }; \\ - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var example: Example = .{ .x = 5, .y = 10 }; \\ return example.y + example.x; \\} , "15\n"); case.addCompareOutput( - \\const Example = struct { x: u32, y: u32 }; + \\const Example = struct { x: u8, y: u8 }; \\ - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var example: Example = .{ .x = 5, .y = 10 }; \\ var example2: Example = .{ .x = 10, .y = 20 }; \\ @@ -531,9 +554,9 @@ pub fn addCases(ctx: *TestContext) !void { , "30\n"); case.addCompareOutput( - \\const Example = struct { x: u32, y: u32 }; + \\const Example = struct { x: u8, y: u8 }; \\ - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var example: Example = .{ .x = 5, .y = 10 }; \\ \\ example = .{ .x = 10, .y = 20 }; @@ -546,9 +569,9 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm switch", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var val: u32 = 1; - \\ var a: u32 = switch (val) { + \\pub fn main() u8 { + \\ var val: u8 = 1; + \\ var a: u8 = switch (val) { \\ 0, 1 => 2, \\ 2 => 3, \\ 3 => 4, @@ -560,9 +583,9 @@ pub fn addCases(ctx: *TestContext) !void { , "2\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var val: u32 = 2; - \\ var a: u32 = switch (val) { + \\pub fn main() u8 { + \\ var val: u8 = 2; + \\ var a: u8 = switch (val) { \\ 0, 1 => 2, \\ 2 => 3, \\ 3 => 4, @@ -574,9 +597,9 @@ pub fn addCases(ctx: *TestContext) !void { , "3\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var val: u32 = 10; - \\ var a: u32 = switch (val) { + \\pub fn main() u8 { + \\ var val: u8 = 10; + \\ var a: u8 = switch (val) { \\ 0, 1 => 2, \\ 2 => 3, \\ 3 => 4, @@ -590,9 +613,9 @@ pub fn addCases(ctx: *TestContext) !void { case.addCompareOutput( \\const MyEnum = enum { One, Two, Three }; \\ - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var val: MyEnum = .Two; - \\ var a: u32 = switch (val) { + \\ var a: u8 = switch (val) { \\ .One => 1, \\ .Two => 2, \\ .Three => 3, @@ -607,7 +630,7 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm error unions", wasi); case.addCompareOutput( - \\pub export fn _start() void { + \\pub fn main() void { \\ var e1 = error.Foo; \\ var e2 = error.Bar; \\ assert(e1 != e2); @@ -618,32 +641,32 @@ pub fn addCases(ctx: *TestContext) !void { \\fn assert(b: bool) void { \\ if (!b) unreachable; \\} - , ""); + , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var e: anyerror!u32 = 5; + \\pub fn main() u8 { + \\ var e: anyerror!u8 = 5; \\ const i = e catch 10; \\ return i; \\} , "5\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var e: anyerror!u32 = error.Foo; + \\pub fn main() u8 { + \\ var e: anyerror!u8 = error.Foo; \\ const i = e catch 10; \\ return i; \\} , "10\n"); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var e = foo(); \\ const i = e catch 69; \\ return i; \\} \\ - \\fn foo() anyerror!u32 { + \\fn foo() anyerror!u8 { \\ return 5; \\} , "5\n"); @@ -653,24 +676,24 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm error union part 2", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var e = foo(); \\ const i = e catch 69; \\ return i; \\} \\ - \\fn foo() anyerror!u32 { + \\fn foo() anyerror!u8 { \\ return error.Bruh; \\} , "69\n"); case.addCompareOutput( - \\pub export fn _start() u32 { + \\pub fn main() u8 { \\ var e = foo(); \\ const i = e catch 42; \\ return i; \\} \\ - \\fn foo() anyerror!u32 { + \\fn foo() anyerror!u8 { \\ return error.Dab; \\} , "42\n"); @@ -680,20 +703,22 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm integer widening", wasi); case.addCompareOutput( - \\pub export fn _start() u64 { - \\ var x: u32 = 5; - \\ return x; + \\pub fn main() void{ + \\ var x: u8 = 5; + \\ var y: u64 = x; + \\ _ = y; + \\ return; \\} - , "5\n"); + , "0\n"); } { var case = ctx.exe("wasm optionals", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var x: ?u32 = 5; - \\ var y: u32 = 0; + \\pub fn main() u8 { + \\ var x: ?u8 = 5; + \\ var y: u8 = 0; \\ if (x) |val| { \\ y = val; \\ } @@ -702,9 +727,9 @@ pub fn addCases(ctx: *TestContext) !void { , "5\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var x: ?u32 = null; - \\ var y: u32 = 0; + \\pub fn main() u8 { + \\ var x: ?u8 = null; + \\ var y: u8 = 0; \\ if (x) |val| { \\ y = val; \\ } @@ -713,23 +738,23 @@ pub fn addCases(ctx: *TestContext) !void { , "0\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var x: ?u32 = 5; + \\pub fn main() u8 { + \\ var x: ?u8 = 5; \\ return x.?; \\} , "5\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var x: u32 = 5; - \\ var y: ?u32 = x; + \\pub fn main() u8 { + \\ var x: u8 = 5; + \\ var y: ?u8 = x; \\ return y.?; \\} , "5\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var val: ?u32 = 5; + \\pub fn main() u8 { + \\ var val: ?u8 = 5; \\ while (val) |*v| { \\ v.* -= 1; \\ if (v.* == 2) { @@ -745,32 +770,32 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("wasm pointers", wasi); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var x: u32 = 0; + \\pub fn main() u8 { + \\ var x: u8 = 0; \\ \\ foo(&x); \\ return x; \\} \\ - \\fn foo(x: *u32)void { + \\fn foo(x: *u8)void { \\ x.* = 2; \\} , "2\n"); case.addCompareOutput( - \\pub export fn _start() u32 { - \\ var x: u32 = 0; + \\pub fn main() u8 { + \\ var x: u8 = 0; \\ \\ foo(&x); \\ bar(&x); \\ return x; \\} \\ - \\fn foo(x: *u32)void { + \\fn foo(x: *u8)void { \\ x.* = 2; \\} \\ - \\fn bar(x: *u32) void { + \\fn bar(x: *u8) void { \\ x.* += 2; \\} , "4\n");