authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-25 00:33:29+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-25 17:56:03+02:00
logf91503e5773ddf4ce3989533ed586ace81e41e90
tree96d51f70cbb191314395446d7354d62c861755f0
parent0078d36ff39f2ef35e32f2d58e5d447b62f3a37e

link-tests: defer parsing of the RPN program until running the action


2 files changed, 138 insertions(+), 83 deletions(-)

lib/std/build/CheckObjectStep.zig+137-82
...@@ -3,6 +3,7 @@ const assert = std.debug.assert;...@@ -3,6 +3,7 @@ const assert = std.debug.assert;
3const build = std.build;3const build = std.build;
4const fs = std.fs;4const fs = std.fs;
5const macho = std.macho;5const macho = std.macho;
6const math = std.math;
6const mem = std.mem;7const mem = std.mem;
7const testing = std.testing;8const testing = std.testing;
89
...@@ -36,20 +37,24 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe...@@ -36,20 +37,24 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe
36 return self;37 return self;
37}38}
3839
39const Action = union(enum) {40/// There two types of actions currently suported:
40 match: MatchAction,41/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}`
41 compute_eq: ComputeEqAction,
42};
43
44/// MatchAction is the main building block of standard matchers with optional eat-all token `{*}`
45/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature42/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature
46/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use43/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use
47/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc.44/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc.
48/// it should be plenty useful in its current form.45/// it should be plenty useful in its current form.
49const MatchAction = struct {46/// * `.compute_cmp` - can be used to perform an operation on the extracted global variables
50 needle: []const u8,47/// using the MatchAction. It currently only supports an addition. The operation is required
48/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well,
49/// to avoid any parsing really).
50/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively
51/// they could then be added with this simple program `vmaddr entryoff +`.
52const Action = struct {
53 tag: enum { match, compute_cmp },
54 phrase: []const u8,
55 expected: ?ComputeCompareExpected = null,
5156
52 /// Will return true if the `needle` was found in the `haystack`.57 /// Will return true if the `phrase` was found in the `haystack`.
53 /// Some examples include:58 /// Some examples include:
54 ///59 ///
55 /// LC 0 => will match in its entirety60 /// LC 0 => will match in its entirety
...@@ -57,9 +62,11 @@ const MatchAction = struct {...@@ -57,9 +62,11 @@ const MatchAction = struct {
57 /// and save under `vmaddr` global name (see `global_vars` param)62 /// and save under `vmaddr` global name (see `global_vars` param)
58 /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib`63 /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib`
59 /// in that order with other letters in between64 /// in that order with other letters in between
60 fn match(act: MatchAction, haystack: []const u8, global_vars: anytype) !bool {65 fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool {
66 assert(act.tag == .match);
67
61 var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");68 var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");
62 var needle_it = mem.tokenize(u8, mem.trim(u8, act.needle, " "), " ");69 var needle_it = mem.tokenize(u8, mem.trim(u8, act.phrase, " "), " ");
6370
64 while (needle_it.next()) |needle_tok| {71 while (needle_it.next()) |needle_tok| {
65 const hay_tok = hay_it.next() orelse return false;72 const hay_tok = hay_it.next() orelse return false;
...@@ -93,22 +100,80 @@ const MatchAction = struct {...@@ -93,22 +100,80 @@ const MatchAction = struct {
93100
94 return true;101 return true;
95 }102 }
96};
97103
98/// ComputeEqAction can be used to perform an operation on the extracted global variables104 /// Will return true if the `phrase` is correctly parsed into an RPN program and
99/// using the MatchAction. It currently only supports an addition. The operation is required105 /// its reduced, computed value compares using `op` with the expected value, either
100/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well,106 /// a literal or another extracted variable.
101/// to avoid any parsing really).107 fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool {
102/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively108 var op_stack = std.ArrayList(enum { add }).init(gpa);
103/// they could then be added with this simple program `vmaddr entryoff +`.109 var values = std.ArrayList(u64).init(gpa);
104const ComputeEqAction = struct {110
105 expected: []const u8,111 var it = mem.tokenize(u8, act.phrase, " ");
106 var_stack: std.ArrayList([]const u8),112 while (it.next()) |next| {
107 op_stack: std.ArrayList(Op),113 if (mem.eql(u8, next, "+")) {
114 try op_stack.append(.add);
115 } else {
116 const val = global_vars.get(next) orelse {
117 std.debug.print(
118 \\
119 \\========= Variable was not extracted: ===========
120 \\{s}
121 \\
122 , .{next});
123 return error.UnknownVariable;
124 };
125 try values.append(val);
126 }
127 }
108128
109 const Op = enum {129 var op_i: usize = 1;
110 add,130 var reduced: u64 = values.items[0];
111 };131 for (op_stack.items) |op| {
132 const other = values.items[op_i];
133 switch (op) {
134 .add => {
135 reduced += other;
136 },
137 }
138 }
139
140 const exp_value = switch (act.expected.?.value) {
141 .variable => |name| global_vars.get(name) orelse {
142 std.debug.print(
143 \\
144 \\========= Variable was not extracted: ===========
145 \\{s}
146 \\
147 , .{name});
148 return error.UnknownVariable;
149 },
150 .literal => |x| x,
151 };
152 return math.compare(reduced, act.expected.?.op, exp_value);
153 }
154};
155
156const ComputeCompareExpected = struct {
157 op: math.CompareOperator,
158 value: union(enum) {
159 variable: []const u8,
160 literal: u64,
161 },
162
163 pub fn format(
164 value: @This(),
165 comptime fmt: []const u8,
166 options: std.fmt.FormatOptions,
167 writer: anytype,
168 ) !void {
169 _ = fmt;
170 _ = options;
171 try writer.print("{s} ", .{@tagName(value.op)});
172 switch (value.value) {
173 .variable => |name| try writer.writeAll(name),
174 .literal => |x| try writer.print("{x}", .{x}),
175 }
176 }
112};177};
113178
114const Check = struct {179const Check = struct {
...@@ -122,15 +187,18 @@ const Check = struct {...@@ -122,15 +187,18 @@ const Check = struct {
122 };187 };
123 }188 }
124189
125 fn match(self: *Check, needle: []const u8) void {190 fn match(self: *Check, phrase: []const u8) void {
126 self.actions.append(.{191 self.actions.append(.{
127 .match = .{ .needle = self.builder.dupe(needle) },192 .tag = .match,
193 .phrase = self.builder.dupe(phrase),
128 }) catch unreachable;194 }) catch unreachable;
129 }195 }
130196
131 fn computeEq(self: *Check, act: ComputeEqAction) void {197 fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void {
132 self.actions.append(.{198 self.actions.append(.{
133 .compute_eq = act,199 .tag = .compute_cmp,
200 .phrase = self.builder.dupe(phrase),
201 .expected = expected,
134 }) catch unreachable;202 }) catch unreachable;
135 }203 }
136};204};
...@@ -165,25 +233,13 @@ pub fn checkInSymtab(self: *CheckObjectStep) void {...@@ -165,25 +233,13 @@ pub fn checkInSymtab(self: *CheckObjectStep) void {
165/// Creates a new standalone, singular check which allows running simple binary operations233/// Creates a new standalone, singular check which allows running simple binary operations
166/// on the extracted variables. It will then compare the reduced program with the value of234/// on the extracted variables. It will then compare the reduced program with the value of
167/// the expected variable.235/// the expected variable.
168pub fn checkComputeEq(self: *CheckObjectStep, program: []const u8, expected: []const u8) void {236pub fn checkComputeCompare(
169 const gpa = self.builder.allocator;237 self: *CheckObjectStep,
170 var ca = ComputeEqAction{238 program: []const u8,
171 .expected = expected,239 expected: ComputeCompareExpected,
172 .var_stack = std.ArrayList([]const u8).init(gpa),240) void {
173 .op_stack = std.ArrayList(ComputeEqAction.Op).init(gpa),
174 };
175
176 var it = mem.tokenize(u8, program, " ");
177 while (it.next()) |next| {
178 if (mem.eql(u8, next, "+")) {
179 ca.op_stack.append(.add) catch unreachable;
180 } else {
181 ca.var_stack.append(self.builder.dupe(next)) catch unreachable;
182 }
183 }
184
185 var new_check = Check.create(self.builder);241 var new_check = Check.create(self.builder);
186 new_check.computeEq(ca);242 new_check.computeCmp(program, expected);
187 self.checks.append(new_check) catch unreachable;243 self.checks.append(new_check) catch unreachable;
188}244}
189245
...@@ -210,10 +266,10 @@ fn make(step: *Step) !void {...@@ -210,10 +266,10 @@ fn make(step: *Step) !void {
210 for (self.checks.items) |chk| {266 for (self.checks.items) |chk| {
211 var it = mem.tokenize(u8, output, "\r\n");267 var it = mem.tokenize(u8, output, "\r\n");
212 for (chk.actions.items) |act| {268 for (chk.actions.items) |act| {
213 switch (act) {269 switch (act.tag) {
214 .match => |match_act| {270 .match => {
215 while (it.next()) |line| {271 while (it.next()) |line| {
216 if (try match_act.match(line, &vars)) break;272 if (try act.match(line, &vars)) break;
217 } else {273 } else {
218 std.debug.print(274 std.debug.print(
219 \\275 \\
...@@ -222,51 +278,33 @@ fn make(step: *Step) !void {...@@ -222,51 +278,33 @@ fn make(step: *Step) !void {
222 \\========= But parsed file does not contain it: =======278 \\========= But parsed file does not contain it: =======
223 \\{s}279 \\{s}
224 \\280 \\
225 , .{ match_act.needle, output });281 , .{ act.phrase, output });
226 return error.TestFailed;282 return error.TestFailed;
227 }283 }
228 },284 },
229 .compute_eq => |c_eq| {285 .compute_cmp => {
230 var values = std.ArrayList(u64).init(gpa);286 const res = act.computeCmp(gpa, vars) catch |err| switch (err) {
231 try values.ensureTotalCapacity(c_eq.var_stack.items.len);287 error.UnknownVariable => {
232 for (c_eq.var_stack.items) |vv| {
233 const val = vars.get(vv) orelse {
234 std.debug.print(288 std.debug.print(
235 \\
236 \\========= Variable was not extracted: ===========
237 \\{s}
238 \\========= From parsed file: =====================289 \\========= From parsed file: =====================
239 \\{s}290 \\{s}
240 \\291 \\
241 , .{ vv, output });292 , .{output});
242 return error.TestFailed;293 return error.TestFailed;
243 };294 },
244 values.appendAssumeCapacity(val);295 else => |e| return e,
245 }296 };
246297 if (!res) {
247 var op_i: usize = 1;
248 var reduced: u64 = values.items[0];
249 for (c_eq.op_stack.items) |op| {
250 const other = values.items[op_i];
251 switch (op) {
252 .add => {
253 reduced += other;
254 },
255 }
256 }
257
258 const expected = vars.get(c_eq.expected) orelse {
259 std.debug.print(298 std.debug.print(
260 \\299 \\
261 \\========= Variable was not extracted: ===========300 \\========= Comparison failed for action: ===========
262 \\{s}301 \\{s} {s}
263 \\========= From parsed file: =====================302 \\========= From parsed file: =======================
264 \\{s}303 \\{s}
265 \\304 \\
266 , .{ c_eq.expected, output });305 , .{ act.phrase, act.expected.?, output });
267 return error.TestFailed;306 return error.TestFailed;
268 };307 }
269 try testing.expectEqual(reduced, expected);
270 },308 },
271 }309 }
272 }310 }
...@@ -349,6 +387,23 @@ const MachODumper = struct {...@@ -349,6 +387,23 @@ const MachODumper = struct {
349 seg.fileoff,387 seg.fileoff,
350 seg.filesize,388 seg.filesize,
351 });389 });
390
391 for (lc.segment.sections.items) |sect| {
392 try writer.writeByte('\n');
393 try writer.print(
394 \\sectname {s}
395 \\addr {x}
396 \\size {x}
397 \\offset {x}
398 \\align {x}
399 , .{
400 sect.sectName(),
401 sect.addr,
402 sect.size,
403 sect.offset,
404 sect.@"align",
405 });
406 }
352 },407 },
353408
354 .ID_DYLIB,409 .ID_DYLIB,
test/link/macho/entry/build.zig+1-1
...@@ -24,7 +24,7 @@ pub fn build(b: *Builder) void {...@@ -24,7 +24,7 @@ pub fn build(b: *Builder) void {
24 check_exe.checkInSymtab();24 check_exe.checkInSymtab();
25 check_exe.checkNext("_non_main {n_value}");25 check_exe.checkNext("_non_main {n_value}");
2626
27 check_exe.checkComputeEq("vmaddr entryoff +", "n_value");27 check_exe.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } });
2828
29 test_step.dependOn(&check_exe.step);29 test_step.dependOn(&check_exe.step);
3030