authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-04-08 22:44:29+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-04-08 22:47:08+02:00
logff5774d93d9d952a74fab3666d4480f534c770db
tree910dfe9685f5acd65efead91c86ef758a452e702
parent47f36427887fc7d1646cfb7ed8eeeeb14cd3555b
signaturelock-open Commit is signed but in an unrecognized format.

Refactor link/wasm.zig to use offset table

This refactor inserts an offset table into wasm's data section where each offset points to the actual data region. This means we can keep offset indexes consistant and do not have to perform any computer to determine where in the data section something like a static string exists. Instead during runtime it will load the data offset onto the stack.

5 files changed, 238 insertions(+), 176 deletions(-)

lib/std/wasm.zig+3
......@@ -280,3 +280,6 @@ pub const block_empty: u8 = 0x40;
280280// binary constants
281281pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
282282pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1
283
284// Each wasm page size is 64kB
285pub const page_size = 64 * 1024;
src/Module.zig+8-5
......@@ -3029,9 +3029,12 @@ fn astgenAndSemaVarDecl(
30293029 };
30303030 defer gen_scope.instructions.deinit(mod.gpa);
30313031
3032 const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0) .{
3033 .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),
3034 } else .none;
3032 const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0)
3033 .{
3034 .ty = try AstGen.expr(&gen_scope, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node),
3035 }
3036 else
3037 .none;
30353038
30363039 const init_inst = try AstGen.comptimeExpr(
30373040 &gen_scope,
......@@ -3834,7 +3837,7 @@ fn allocateNewDecl(
38343837 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
38353838 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
38363839 .c => .{ .c = link.File.C.DeclBlock.empty },
3837 .wasm => .{ .wasm = {} },
3840 .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty },
38383841 .spirv => .{ .spirv = {} },
38393842 },
38403843 .fn_link = switch (mod.comp.bin_file.tag) {
......@@ -3842,7 +3845,7 @@ fn allocateNewDecl(
38423845 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
38433846 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
38443847 .c => .{ .c = link.File.C.FnBlock.empty },
3845 .wasm => .{ .wasm = .{} },
3848 .wasm => .{ .wasm = link.File.Wasm.FnData.empty },
38463849 .spirv => .{ .spirv = .{} },
38473850 },
38483851 .generation = 0,
src/codegen/wasm.zig+32-7
......@@ -543,11 +543,20 @@ pub const Context = struct {
543543
544544 /// Using a given `Type`, returns the corresponding wasm Valtype
545545 fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {
546 return switch (ty.tag()) {
547 .f32 => .f32,
548 .f64 => .f64,
549 .u32, .i32, .bool => .i32,
550 .u64, .i64 => .i64,
546 return switch (ty.zigTypeTag()) {
547 .Float => blk: {
548 const bits = ty.floatBits(self.target);
549 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
550 if (bits == 64) break :blk wasm.Valtype.f64;
551 return self.fail(src, "Float bit size not supported by wasm: '{d}'", .{bits});
552 },
553 .Int => blk: {
554 const info = ty.intInfo(self.target);
555 if (info.bits <= 32) break :blk wasm.Valtype.i32;
556 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
557 return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
558 },
559 .Bool, .Pointer => wasm.Valtype.i32,
551560 else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),
552561 };
553562 }
......@@ -624,7 +633,7 @@ pub const Context = struct {
624633 const mod_fn = blk: {
625634 if (typed_value.val.castTag(.function)) |func| break :blk func.data;
626635 if (typed_value.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
627 return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{typed_value.ty.tag()});
636 unreachable;
628637 };
629638
630639 // Reserve space to write the size after generating the code as well as space for locals count
......@@ -680,7 +689,7 @@ pub const Context = struct {
680689 } else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
681690 },
682691 .Int => {
683 const info = typed_value.ty.intInfo(self.bin_file.base.options.target);
692 const info = typed_value.ty.intInfo(self.target);
684693 if (info.bits == 8 and info.signedness == .unsigned) {
685694 const int_byte = typed_value.val.toUnsignedInt();
686695 try self.code.append(@intCast(u8, int_byte));
......@@ -856,6 +865,22 @@ pub const Context = struct {
856865 else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),
857866 }
858867 },
868 .Pointer => {
869 if (inst.val.castTag(.decl_ref)) |payload| {
870 const decl = payload.data;
871
872 // offset into the offset table within the 'data' section
873 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
874 try writer.writeByte(wasm.opcode(.i32_const));
875 try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width);
876
877 // memory instruction followed by their memarg immediate
878 // memarg ::== x:u32, y:u32 => {align x, offset y}
879 try writer.writeByte(wasm.opcode(.i32_load));
880 try leb.writeULEB128(writer, @as(u32, 0));
881 try leb.writeULEB128(writer, @as(u32, 0));
882 } else return self.fail(inst.base.src, "Wasm TODO: emitConstant for other const pointer tag {s}", .{inst.val.tag()});
883 },
859884 .Void => {},
860885 else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),
861886 }
src/link.zig+1-1
......@@ -138,7 +138,7 @@ pub const File = struct {
138138 coff: Coff.TextBlock,
139139 macho: MachO.TextBlock,
140140 c: C.DeclBlock,
141 wasm: void,
141 wasm: Wasm.DeclBlock,
142142 spirv: void,
143143 };
144144
src/link/Wasm.zig+194-163
......@@ -20,70 +20,7 @@ const TypedValue = @import("../TypedValue.zig");
2020
2121pub const base_tag = link.File.Tag.wasm;
2222
23pub const FnData = struct {
24 /// Generated code for the type of the function
25 functype: std.ArrayListUnmanaged(u8) = .{},
26 /// Generated code for the body of the function
27 code: std.ArrayListUnmanaged(u8) = .{},
28 /// Locations in the generated code where function indexes must be filled in.
29 /// This must be kept ordered by offset.
30 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
31};
32
33/// Data section of the wasm binary
34/// Each declaration will have its own 'data_segment' within the section
35/// where the offset is calculated using the previous segments and the content length
36/// of the data
37pub const DataSection = struct {
38 /// Every data object will be appended to this list,
39 /// containing its `Decl`, the data in bytes, and its length.
40 segments: std.ArrayListUnmanaged(struct {
41 /// The decl that lives inside the 'data' section such as an array
42 decl: *Module.Decl,
43 /// The contents of the data in bytes
44 data: [*]const u8,
45 /// The length of the contents inside the 'data' section
46 len: u32,
47 }) = .{},
48
49 /// Returns the offset into the data segment based on a given `Decl`
50 pub fn offset(self: DataSection, decl: *const Module.Decl) u32 {
51 var cur_offset: u32 = 0;
52 return for (self.segments.items) |entry| {
53 if (entry.decl == decl) break cur_offset;
54 cur_offset += entry.len;
55 } else unreachable; // offset() called on declaration that does not live inside 'data' section
56 }
57
58 /// Returns the total payload size of the data section
59 pub fn size(self: DataSection) u32 {
60 var total: u32 = 0;
61 for (self.segments.items) |entry| {
62 total += entry.len;
63 }
64 return total;
65 }
66
67 /// Updates the data in the data segment belonging to the given decl.
68 /// It's illegal behaviour to call this before allocateDeclIndexes was called
69 /// `data` must be managed externally with a lifetime that last as long as codegen does.
70 pub fn updateData(self: DataSection, decl: *Module.Decl, data: []const u8) void {
71 const entry = for (self.segments.items) |*item| {
72 if (item.decl == decl) break item;
73 } else unreachable; // called updateData before the declaration was added to data segments
74 entry.data = data.ptr;
75 }
76
77 /// Returns the index of a declaration and `null` when not found
78 pub fn getIdx(self: DataSection, decl: *Module.Decl) ?usize {
79 return for (self.segments.items) |entry, i| {
80 if (entry.decl == decl) break i;
81 } else null;
82 }
83};
84
8523base: link.File,
86
8724/// List of all function Decls to be written to the output file. The index of
8825/// each Decl in this list at the time of writing the binary is used as the
8926/// function index. In the event where ext_funcs' size is not 0, the index of
......@@ -98,10 +35,67 @@ ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
9835/// to support existing code.
9936/// TODO: Allow setting this through a flag?
10037host_name: []const u8 = "env",
101/// Map of declarations with its bytes payload, used to keep track of all data segments
102/// that needs to be emit when creating the wasm binary.
103/// The `DataSection`'s lifetime must be kept alive until the linking stage.
104data: DataSection = .{},
38/// The last `DeclBlock` that was initialized will be saved here.
39last_block: ?*DeclBlock = null,
40/// Table with offsets, each element represents an offset with the value being
41/// the offset into the 'data' section where the data lives
42offset_table: std.ArrayListUnmanaged(u32) = .{},
43/// List of offset indexes which are free to be used for new decl's.
44/// Each element's value points to an index into the offset_table.
45offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
46/// List of all `Decl` that are currently alive.
47/// This is ment for bookkeeping so we can safely cleanup all codegen memory
48/// when calling `deinit`
49symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},
50/// Contains indexes into `symbols` that are no longer used and can be populated instead,
51/// removing the need to search for a symbol and remove it when it's dereferenced.
52symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
53
54pub const FnData = struct {
55 /// Generated code for the type of the function
56 functype: std.ArrayListUnmanaged(u8),
57 /// Generated code for the body of the function
58 code: std.ArrayListUnmanaged(u8),
59 /// Locations in the generated code where function indexes must be filled in.
60 /// This must be kept ordered by offset.
61 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }),
62
63 pub const empty: FnData = .{
64 .functype = .{},
65 .code = .{},
66 .idx_refs = .{},
67 };
68};
69
70pub const DeclBlock = struct {
71 /// Determines whether the `DeclBlock` has been initialized for codegen.
72 init: bool,
73 /// Index into the `symbols` list.
74 symbol_index: u32,
75 /// Index into the offset table
76 offset_index: u32,
77 /// The size of the block and how large part of the data section it occupies.
78 /// Will be 0 when the Decl will not live inside the data section and `data` will be undefined.
79 size: u32,
80 /// Points to the previous and next blocks.
81 /// Can be used to find the total size, and used to calculate the `offset` based on the previous block.
82 prev: ?*DeclBlock,
83 next: ?*DeclBlock,
84 /// Pointer to data that will be written to the 'data' section.
85 /// This data either lives in `FnData.code` or is externally managed.
86 /// For data that does not live inside the 'data' section, this field will be undefined. (size == 0).
87 data: [*]const u8,
88
89 pub const empty: DeclBlock = .{
90 .init = false,
91 .symbol_index = 0,
92 .offset_index = 0,
93 .size = 0,
94 .prev = null,
95 .next = null,
96 .data = undefined,
97 };
98};
10599
106100pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
107101 assert(options.object_format == .wasm);
......@@ -137,94 +131,66 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Wasm {
137131}
138132
139133pub fn deinit(self: *Wasm) void {
140 for (self.funcs.items) |decl| {
141 decl.fn_link.wasm.functype.deinit(self.base.allocator);
142 decl.fn_link.wasm.code.deinit(self.base.allocator);
143 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
134 while (self.symbols_free_list.popOrNull()) |idx| {
135 //dead decl's so remove them from symbol list before trying to clean them up
136 _ = self.symbols.swapRemove(idx);
144137 }
145 for (self.ext_funcs.items) |decl| {
138 for (self.symbols.items) |decl| {
146139 decl.fn_link.wasm.functype.deinit(self.base.allocator);
147140 decl.fn_link.wasm.code.deinit(self.base.allocator);
148141 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
149142 }
150 for (self.data.segments.items) |entry| {
151 // decl's that live in data section do not generate idx_refs or func types
152 entry.decl.fn_link.wasm.code.deinit(self.base.allocator);
153 }
143
154144 self.funcs.deinit(self.base.allocator);
155145 self.ext_funcs.deinit(self.base.allocator);
156 self.data.segments.deinit(self.base.allocator);
146 self.offset_table.deinit(self.base.allocator);
147 self.offset_table_free_list.deinit(self.base.allocator);
148 self.symbols.deinit(self.base.allocator);
149 self.symbols_free_list.deinit(self.base.allocator);
157150}
158151
159152pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
160 const typed_value = decl.typed_value.most_recent.typed_value;
153 if (decl.link.wasm.init) return;
161154
162 switch (typed_value.ty.zigTypeTag()) {
163 .Array => {
164 // if the codegen of the given decl contributes to the data segment
165 // we must calculate its data length now so that the data offsets are available
166 // to other decls when called
167 const data_len = self.calcDataLen(typed_value);
168 try self.data.segments.append(self.base.allocator, .{
169 .decl = decl,
170 .data = undefined,
171 .len = data_len,
172 });
155 try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1);
156 try self.symbols.ensureCapacity(self.base.allocator, self.symbols.items.len + 1);
173157
174 // detect if we can replace it into a to-be-deleted decl's spot to ensure no gaps are
175 // made in our data segment
176 const idx: ?usize = for (self.data.segments.items) |entry, i| {
177 if (entry.decl.deletion_flag) break i;
178 } else null;
179
180 if (idx) |id| {
181 // swap to-be-removed decl with newly added to create a contigious valid data segment
182 const items = self.data.segments.items;
183 std.mem.swap(
184 std.meta.Child(@TypeOf(items)),
185 &items[id],
186 &items[items.len - 1],
187 );
188 }
189 },
190 .Fn => if (self.getFuncidx(decl) == null) switch (typed_value.val.tag()) {
158 const block = &decl.link.wasm;
159 block.init = true;
160
161 if (self.symbols_free_list.popOrNull()) |index| {
162 block.symbol_index = index;
163 } else {
164 block.symbol_index = @intCast(u32, self.symbols.items.len);
165 _ = self.symbols.addOneAssumeCapacity();
166 }
167
168 if (self.offset_table_free_list.popOrNull()) |index| {
169 block.offset_index = index;
170 } else {
171 block.offset_index = @intCast(u32, self.offset_table.items.len);
172 _ = self.offset_table.addOneAssumeCapacity();
173 }
174
175 self.offset_table.items[block.offset_index] = 0;
176
177 const typed_value = decl.typed_value.most_recent.typed_value;
178 if (typed_value.ty.zigTypeTag() == .Fn) {
179 switch (typed_value.val.tag()) {
191180 // dependent on function type, appends it to the correct list
192181 .function => try self.funcs.append(self.base.allocator, decl),
193182 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
194183 else => unreachable,
195 },
196 else => {},
197 }
198}
199
200/// Calculates the length of the data segment that will be occupied by the given `TypedValue`
201fn calcDataLen(self: *Wasm, typed_value: TypedValue) u32 {
202 switch (typed_value.ty.zigTypeTag()) {
203 .Array => {
204 if (typed_value.val.castTag(.bytes)) |payload| {
205 if (typed_value.ty.sentinel()) |sentinel| {
206 return @intCast(u32, payload.data.len) + self.calcDataLen(.{
207 .ty = typed_value.ty.elemType(),
208 .val = sentinel,
209 });
210 }
211 }
212 return @intCast(u32, typed_value.ty.arrayLen());
213 },
214 .Int => {
215 const info = typed_value.ty.intInfo(self.base.options.target);
216 return std.math.divCeil(u32, info.bits, 8) catch unreachable;
217 },
218 .Pointer => return 4,
219 else => unreachable,
184 }
220185 }
221186}
222187
223188// Generate code for the Decl, storing it in memory to be later written to
224189// the file on flush().
225190pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
226 const typed_value = decl.typed_value.most_recent.typed_value;
191 std.debug.assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
227192
193 const typed_value = decl.typed_value.most_recent.typed_value;
228194 const fn_data = &decl.fn_link.wasm;
229195 fn_data.functype.items.len = 0;
230196 fn_data.code.items.len = 0;
......@@ -252,28 +218,43 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
252218 else => |e| return err,
253219 };
254220
255 switch (typed_value.ty.zigTypeTag()) {
256 .Fn => {
257 // as locals are patched afterwards, the offsets of funcidx's are off,
258 // here we update them to correct them
259 for (fn_data.idx_refs.items) |*func| {
260 // For each local, add 6 bytes (count + type)
261 func.offset += @intCast(u32, context.locals.items.len * 6);
262 }
221 const code: []const u8 = switch (result) {
222 .appended => @as([]const u8, context.code.items),
223 .externally_managed => |payload| payload,
224 };
263225
264 fn_data.functype = context.func_type_data.toUnmanaged();
265 fn_data.code = context.code.toUnmanaged();
266 },
267 .Array => switch (result) {
268 .appended => {
269 fn_data.functype = context.func_type_data.toUnmanaged();
270 fn_data.code = context.code.toUnmanaged();
271 self.data.updateData(decl, fn_data.code.items);
272 },
273 .externally_managed => |payload| self.data.updateData(decl, payload),
274 },
275 else => return error.TODO,
226 fn_data.code = context.code.toUnmanaged();
227 fn_data.functype = context.func_type_data.toUnmanaged();
228
229 const block = &decl.link.wasm;
230 if (typed_value.ty.zigTypeTag() == .Fn) {
231 // as locals are patched afterwards, the offsets of funcidx's are off,
232 // here we update them to correct them
233 for (fn_data.idx_refs.items) |*func| {
234 // For each local, add 6 bytes (count + type)
235 func.offset += @intCast(u32, context.locals.items.len * 6);
236 }
237 } else {
238 block.size = @intCast(u32, code.len);
239 block.data = code.ptr;
240 }
241
242 // If we're updating an existing decl, unplug it first
243 // to avoid infinite loops due to earlier links
244 if (block.prev) |prev| {
245 prev.next = block.next;
246 }
247 if (block.next) |next| {
248 next.prev = block.prev;
276249 }
250
251 if (self.last_block) |last| {
252 if (last != block) {
253 last.next = block;
254 block.prev = last;
255 }
256 }
257 self.last_block = block;
277258}
278259
279260pub fn updateDeclExports(
......@@ -291,9 +272,24 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
291272 else => unreachable,
292273 }
293274 }
294 if (self.data.getIdx(decl)) |idx| {
295 _ = self.data.segments.swapRemove(idx);
275 const block = &decl.link.wasm;
276
277 if (self.last_block == block) {
278 self.last_block = block.prev;
279 }
280
281 if (block.prev) |prev| {
282 prev.next = block.next;
296283 }
284
285 if (block.next) |next| {
286 next.prev = block.prev;
287 }
288
289 self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};
290 self.symbols_free_list.append(self.base.allocator, decl.link.wasm.symbol_index) catch {};
291 block.init = false;
292
297293 decl.fn_link.wasm.functype.deinit(self.base.allocator);
298294 decl.fn_link.wasm.code.deinit(self.base.allocator);
299295 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
......@@ -314,7 +310,25 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
314310
315311 const file = self.base.file.?;
316312 const header_size = 5 + 1;
317 const data_size = self.data.size();
313 // ptr_width in bytes
314 const ptr_width = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
315 // The size of the offset table in bytes
316 // The table contains all decl's with its corresponding offset into
317 // the 'data' section
318 const offset_table_size = @intCast(u32, self.offset_table.items.len * ptr_width);
319
320 // The size of the data, this together with `offset_table_size` amounts to the
321 // total size of the 'data' section
322 var first_decl: ?*DeclBlock = null;
323 const data_size: u32 = if (self.last_block) |last| blk: {
324 var size = last.size;
325 var cur = last;
326 while (cur.prev) |prev| : (cur = prev) {
327 size += prev.size;
328 }
329 first_decl = cur;
330 break :blk size;
331 } else 0;
318332
319333 // No need to rewrite the magic/version header
320334 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
......@@ -396,8 +410,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
396410 writer,
397411 try std.math.divCeil(
398412 u32,
399 self.data.size(),
400 std.mem.page_size,
413 offset_table_size + data_size,
414 std.wasm.page_size,
401415 ),
402416 );
403417 try writeVecSectionHeader(
......@@ -496,18 +510,35 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
496510 try leb.writeILEB128(writer, @as(i32, 0));
497511 try writer.writeByte(wasm.opcode(.end));
498512
499 // payload size
500 try leb.writeULEB128(writer, data_size);
513 const total_size = offset_table_size + data_size;
501514
502 // write payload
503 for (self.data.segments.items) |entry| try writer.writeAll(entry.data[0..entry.len]);
515 // offset table + data size
516 try leb.writeULEB128(writer, total_size);
504517
518 // fill in the offset table and the data segments
519 const file_offset = try file.getPos();
520 var cur = first_decl;
521 var data_offset = offset_table_size;
522 while (cur) |cur_block| : (cur = cur_block.next) {
523 if (cur_block.size == 0) continue;
524 std.debug.assert(cur_block.init);
525
526 const offset = (cur_block.offset_index) * ptr_width;
527 var buf: [4]u8 = undefined;
528 std.mem.writeIntLittle(u32, &buf, data_offset);
529
530 try file.pwriteAll(&buf, file_offset + offset);
531 try file.pwriteAll(cur_block.data[0..cur_block.size], file_offset + data_offset);
532 data_offset += cur_block.size;
533 }
534
535 try file.seekTo(file_offset + data_offset);
505536 try writeVecSectionHeader(
506537 file,
507538 header_offset,
508539 .data,
509 @intCast(u32, (try file.getPos()) - header_offset - header_size),
510 @intCast(u32, 1),
540 @intCast(u32, (file_offset + data_offset) - header_offset - header_size),
541 @intCast(u32, 1), // only 1 data section
511542 );
512543 }
513544}