Commit 00b2e31589

Luuk de Gram <Luukdegram@users.noreply.github.com>
2021-03-27 23:26:20
Basic "Hello world" working
1 parent 9f744f1
Changed files (2)
src
codegen
link
src/codegen/wasm.zig
@@ -163,25 +163,23 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
         .global_get => return .global_get,
         .global_set => return .global_set,
 
-        .load => if (args.width) |width|
-            switch (width) {
-                8 => switch (args.valtype1.?) {
-                    .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
-                    .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
-                    .f32, .f64 => unreachable,
-                },
-                16 => switch (args.valtype1.?) {
-                    .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
-                    .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
-                    .f32, .f64 => unreachable,
-                },
-                32 => switch (args.valtype1.?) {
-                    .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
-                    .i32, .f32, .f64 => unreachable,
-                },
-                else => unreachable,
-            }
-        else switch (args.valtype1.?) {
+        .load => if (args.width) |width| switch (width) {
+            8 => switch (args.valtype1.?) {
+                .i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
+                .i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
+                .f32, .f64 => unreachable,
+            },
+            16 => switch (args.valtype1.?) {
+                .i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
+                .i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
+                .f32, .f64 => unreachable,
+            },
+            32 => switch (args.valtype1.?) {
+                .i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
+                .i32, .f32, .f64 => unreachable,
+            },
+            else => unreachable,
+        } else switch (args.valtype1.?) {
             .i32 => return .i32_load,
             .i64 => return .i64_load,
             .f32 => return .f32_load,
@@ -469,6 +467,13 @@ test "Wasm - buildOpcode" {
     testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
 }
 
+pub const Result = union(enum) {
+    /// The codegen bytes have been appended to `Context.code`
+    appended: void,
+    /// The data is managed externally and are part of the `Result`
+    externally_managed: []const u8,
+};
+
 /// Hashmap to store generated `WValue` for each `Inst`
 pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
 
@@ -504,6 +509,8 @@ pub const Context = struct {
     const InnerError = error{
         OutOfMemory,
         CodegenFail,
+        /// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
+        AnalysisFail,
     };
 
     pub fn deinit(self: *Context) void {
@@ -604,48 +611,65 @@ pub const Context = struct {
     }
 
     /// Generates the wasm bytecode for the function declaration belonging to `Context`
-    pub fn gen(self: *Context) InnerError!void {
+    pub fn gen(self: *Context) InnerError!Result {
         assert(self.code.items.len == 0);
-        try self.genFunctype();
 
-        // Write instructions
-        // TODO: check for and handle death of instructions
         const tv = self.decl.typed_value.most_recent.typed_value;
-        const mod_fn = blk: {
-            if (tv.val.castTag(.function)) |func| break :blk func.data;
-            if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions
-            return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
-        };
-
-        // Reserve space to write the size after generating the code as well as space for locals count
-        try self.code.resize(10);
-
-        try self.genBody(mod_fn.body);
+        switch (tv.ty.zigTypeTag()) {
+            .Fn => {
+                try self.genFunctype();
+
+                // Write instructions
+                // TODO: check for and handle death of instructions
+                const mod_fn = blk: {
+                    if (tv.val.castTag(.function)) |func| break :blk func.data;
+                    if (tv.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
+                    return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
+                };
+
+                // Reserve space to write the size after generating the code as well as space for locals count
+                try self.code.resize(10);
+
+                try self.genBody(mod_fn.body);
+
+                // finally, write our local types at the 'offset' position
+                {
+                    leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
+
+                    // offset into 'code' section where we will put our locals types
+                    var local_offset: usize = 10;
+
+                    // emit the actual locals amount
+                    for (self.locals.items) |local| {
+                        var buf: [6]u8 = undefined;
+                        leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
+                        buf[5] = local;
+                        try self.code.insertSlice(local_offset, &buf);
+                        local_offset += 6;
+                    }
+                }
 
-        // finally, write our local types at the 'offset' position
-        {
-            leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
+                const writer = self.code.writer();
+                try writer.writeByte(wasm.opcode(.end));
 
-            // offset into 'code' section where we will put our locals types
-            var local_offset: usize = 10;
+                // Fill in the size of the generated code to the reserved space at the
+                // beginning of the buffer.
+                const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
+                leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
 
-            // emit the actual locals amount
-            for (self.locals.items) |local| {
-                var buf: [6]u8 = undefined;
-                leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
-                buf[5] = local;
-                try self.code.insertSlice(local_offset, &buf);
-                local_offset += 6;
-            }
+                // codegen data has been appended to `code`
+                return Result.appended;
+            },
+            .Array => {
+                if (tv.val.castTag(.bytes)) |payload| {
+                    if (tv.ty.sentinel()) |sentinel| {
+                        // TODO, handle sentinel correctly
+                    }
+                    return Result{ .externally_managed = payload.data };
+                } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
+            },
+            else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}),
         }
-
-        const writer = self.code.writer();
-        try writer.writeByte(wasm.opcode(.end));
-
-        // Fill in the size of the generated code to the reserved space at the
-        // beginning of the buffer.
-        const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
-        leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
     }
 
     fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
src/link/Wasm.zig
@@ -29,6 +29,32 @@ pub const FnData = struct {
     idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
 };
 
+/// Data section of the wasm binary
+/// Each declaration will have its own 'data_segment' within the section
+/// where the offset is calculated using the previous segments and the content length
+/// of the data
+pub const DataSection = struct {
+    segments: std.AutoArrayHashMapUnmanaged(*const Module.Decl, []const u8) = .{},
+
+    /// Returns the offset into the data segment based on a given `Decl`
+    pub fn offset(self: DataSection, decl: *const Module.Decl) u32 {
+        var cur_offset: u32 = 0;
+        return for (self.segments.items()) |entry| {
+            if (entry.key == decl) break cur_offset;
+            cur_offset += @intCast(u32, entry.value.len);
+        } else cur_offset;
+    }
+
+    /// Returns the total payload size of the data section
+    pub fn size(self: DataSection) u32 {
+        var total: u32 = 0;
+        for (self.segments.items()) |entry| {
+            total += @intCast(u32, entry.value.len);
+        }
+        return total;
+    }
+};
+
 base: link.File,
 
 /// List of all function Decls to be written to the output file. The index of
@@ -45,6 +71,10 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
 /// to support existing code.
 /// TODO: Allow setting this through a flag?
 host_name: []const u8 = "env",
+/// Map of declarations with its bytes payload, used to keep track of all data segments
+/// that needs to be emit when creating the wasm binary.
+/// The `DataSection`'s lifetime must be kept alive until the linking stage.
+data: DataSection = .{},
 
 pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
     assert(options.object_format == .wasm);
@@ -52,7 +82,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
     if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
     if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
 
-    // TODO: read the file and keep vaild parts instead of truncating
+    // TODO: read the file and keep valid parts instead of truncating
     const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
     errdefer file.close();
 
@@ -92,14 +122,13 @@ pub fn deinit(self: *Wasm) void {
     }
     self.funcs.deinit(self.base.allocator);
     self.ext_funcs.deinit(self.base.allocator);
+    self.data.segments.deinit(self.base.allocator);
 }
 
 // Generate code for the Decl, storing it in memory to be later written to
 // the file on flush().
 pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
     const typed_value = decl.typed_value.most_recent.typed_value;
-    if (typed_value.ty.zigTypeTag() != .Fn)
-        return error.TODOImplementNonFnDeclsForWasm;
 
     if (decl.fn_link.wasm) |*fn_data| {
         fn_data.functype.items.len = 0;
@@ -111,6 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
         switch (decl.typed_value.most_recent.typed_value.val.tag()) {
             .function => try self.funcs.append(self.base.allocator, decl),
             .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
+            .bytes => {},
             else => return error.TODOImplementNonFnDeclsForWasm,
         }
     }
@@ -132,7 +162,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
     defer context.deinit();
 
     // generate the 'code' section for the function declaration
-    context.gen() catch |err| switch (err) {
+    const result = context.gen() catch |err| switch (err) {
         error.CodegenFail => {
             decl.analysis = .codegen_failure;
             try module.failed_decls.put(module.gpa, decl, context.err_msg);
@@ -141,15 +171,24 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
         else => |e| return err,
     };
 
-    // as locals are patched afterwards, the offsets of funcidx's are off,
-    // here we update them to correct them
-    for (decl.fn_link.wasm.?.idx_refs.items) |*func| {
-        // For each local, add 6 bytes (count + type)
-        func.offset += @intCast(u32, context.locals.items.len * 6);
-    }
+    switch (typed_value.ty.zigTypeTag()) {
+        .Fn => {
+            // as locals are patched afterwards, the offsets of funcidx's are off,
+            // here we update them to correct them
+            for (decl.fn_link.wasm.?.idx_refs.items) |*func| {
+                // For each local, add 6 bytes (count + type)
+                func.offset += @intCast(u32, context.locals.items.len * 6);
+            }
 
-    fn_data.functype = context.func_type_data.toUnmanaged();
-    fn_data.code = context.code.toUnmanaged();
+            fn_data.functype = context.func_type_data.toUnmanaged();
+            fn_data.code = context.code.toUnmanaged();
+        },
+        .Array => switch (result) {
+            .appended => unreachable,
+            .externally_managed => |payload| try self.data.segments.put(self.base.allocator, decl, payload),
+        },
+        else => return error.TODO,
+    }
 }
 
 pub fn updateDeclExports(
@@ -257,6 +296,22 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
         );
     }
 
+    // Memory section
+    if (self.data.size() != 0) {
+        const header_offset = try reserveVecSectionHeader(file);
+        const writer = file.writer();
+
+        try leb.writeULEB128(writer, @as(u32, 0));
+        try leb.writeULEB128(writer, @as(u32, 1));
+        try writeVecSectionHeader(
+            file,
+            header_offset,
+            .memory,
+            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @as(u32, 1),
+        );
+    }
+
     // Export section
     if (self.base.options.module) |module| {
         const header_offset = try reserveVecSectionHeader(file);
@@ -281,6 +336,16 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
                 count += 1;
             }
         }
+
+        // export memory if size is not 0
+        if (self.data.size() != 0) {
+            try leb.writeULEB128(writer, @intCast(u32, "memory".len));
+            try writer.writeAll("memory");
+            try writer.writeByte(wasm.externalKind(.memory));
+            try leb.writeULEB128(writer, @as(u32, 0)); // only 1 memory 'object' can exist
+            count += 1;
+        }
+
         try writeVecSectionHeader(
             file,
             header_offset,
@@ -320,6 +385,38 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
             @intCast(u32, self.funcs.items.len),
         );
     }
+
+    // Data section
+    {
+        const header_offset = try reserveVecSectionHeader(file);
+        const writer = file.writer();
+        var offset: i32 = 0;
+        for (self.data.segments.items()) |entry| {
+            // index to memory section (always 0 in current wasm version)
+            try leb.writeULEB128(writer, @as(u32, 0));
+
+            // offset into data section
+            try writer.writeByte(wasm.opcode(.i32_const));
+            try leb.writeILEB128(writer, offset);
+            try writer.writeByte(wasm.opcode(.end));
+
+            // payload size
+            const len = @intCast(u32, entry.value.len);
+            try leb.writeULEB128(writer, len);
+
+            // write payload
+            try writer.writeAll(entry.value);
+            offset += @bitCast(i32, len);
+        }
+
+        try writeVecSectionHeader(
+            file,
+            header_offset,
+            .data,
+            @intCast(u32, (try file.getPos()) - header_offset - header_size),
+            @intCast(u32, self.data.segments.items().len),
+        );
+    }
 }
 
 fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {