| author | |
| committer | |
| log | 1e785409bb77a4ae1c8a496acae64d94c34431b9 |
| tree | 2d1c00e45239ed55181359aa2d3962fb71b2cf67 |
| parent | 028a9a10549e59e84105515ffb50c491f487c91a |
4 files changed, 452 insertions(+), 452 deletions(-)
CMakeLists.txt+1-1| ... | ... | @@ -601,7 +601,6 @@ set(ZIG_STAGE2_SOURCES |
| 601 | 601 | src/link/Elf/Archive.zig |
| 602 | 602 | src/link/Elf/Atom.zig |
| 603 | 603 | src/link/Elf/AtomList.zig |
| 604 | src/link/Elf/LdScript.zig | |
| 605 | 604 | src/link/Elf/LinkerDefined.zig |
| 606 | 605 | src/link/Elf/Merge.zig |
| 607 | 606 | src/link/Elf/Object.zig |
| ... | ... | @@ -615,6 +614,7 @@ set(ZIG_STAGE2_SOURCES |
| 615 | 614 | src/link/Elf/relocatable.zig |
| 616 | 615 | src/link/Elf/relocation.zig |
| 617 | 616 | src/link/Elf/synthetic_sections.zig |
| 617 | src/link/LdScript.zig | |
| 618 | 618 | src/link/MachO.zig |
| 619 | 619 | src/link/MachO/Archive.zig |
| 620 | 620 | src/link/MachO/Atom.zig |
src/link/Elf.zig+1-1| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | pub const Atom = @import("Elf/Atom.zig"); |
| 2 | pub const LdScript = @import("LdScript.zig"); | |
| 2 | 3 | |
| 3 | 4 | base: link.File, |
| 4 | 5 | rpath_table: std.StringArrayHashMapUnmanaged(void), |
| ... | ... | @@ -5625,7 +5626,6 @@ const GnuHashSection = synthetic_sections.GnuHashSection; |
| 5625 | 5626 | const GotSection = synthetic_sections.GotSection; |
| 5626 | 5627 | const GotPltSection = synthetic_sections.GotPltSection; |
| 5627 | 5628 | const HashSection = synthetic_sections.HashSection; |
| 5628 | const LdScript = @import("Elf/LdScript.zig"); | |
| 5629 | 5629 | const LinkerDefined = @import("Elf/LinkerDefined.zig"); |
| 5630 | 5630 | const Liveness = @import("../Liveness.zig"); |
| 5631 | 5631 | const LlvmObject = @import("../codegen/llvm.zig").Object; |
src/link/Elf/LdScript.zig deleted-450| ... | ... | @@ -1,450 +0,0 @@ |
| 1 | path: Path, | |
| 2 | cpu_arch: ?std.Target.Cpu.Arch, | |
| 3 | args: []const Arg, | |
| 4 | ||
| 5 | pub const Arg = struct { | |
| 6 | needed: bool = false, | |
| 7 | path: []const u8, | |
| 8 | }; | |
| 9 | ||
| 10 | pub fn deinit(ls: *LdScript, gpa: Allocator) void { | |
| 11 | gpa.free(ls.args); | |
| 12 | ls.* = undefined; | |
| 13 | } | |
| 14 | ||
| 15 | pub const Error = error{ | |
| 16 | LinkFailure, | |
| 17 | UnexpectedToken, | |
| 18 | UnknownCpuArch, | |
| 19 | OutOfMemory, | |
| 20 | }; | |
| 21 | ||
| 22 | pub fn parse( | |
| 23 | gpa: Allocator, | |
| 24 | diags: *Diags, | |
| 25 | /// For error reporting. | |
| 26 | path: Path, | |
| 27 | data: []const u8, | |
| 28 | ) Error!LdScript { | |
| 29 | var tokenizer = Tokenizer{ .source = data }; | |
| 30 | var tokens: std.ArrayListUnmanaged(Token) = .empty; | |
| 31 | defer tokens.deinit(gpa); | |
| 32 | var line_col: std.ArrayListUnmanaged(LineColumn) = .empty; | |
| 33 | defer line_col.deinit(gpa); | |
| 34 | ||
| 35 | var line: usize = 0; | |
| 36 | var prev_line_last_col: usize = 0; | |
| 37 | ||
| 38 | while (true) { | |
| 39 | const tok = tokenizer.next(); | |
| 40 | try tokens.append(gpa, tok); | |
| 41 | const column = tok.start - prev_line_last_col; | |
| 42 | try line_col.append(gpa, .{ .line = line, .column = column }); | |
| 43 | switch (tok.id) { | |
| 44 | .invalid => { | |
| 45 | return diags.failParse(path, "invalid token in LD script: '{s}' ({d}:{d})", .{ | |
| 46 | std.fmt.fmtSliceEscapeLower(tok.get(data)), line, column, | |
| 47 | }); | |
| 48 | }, | |
| 49 | .new_line => { | |
| 50 | line += 1; | |
| 51 | prev_line_last_col = tok.end; | |
| 52 | }, | |
| 53 | .eof => break, | |
| 54 | else => {}, | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | var it: TokenIterator = .{ .tokens = tokens.items }; | |
| 59 | var parser: Parser = .{ | |
| 60 | .gpa = gpa, | |
| 61 | .source = data, | |
| 62 | .it = &it, | |
| 63 | .args = .empty, | |
| 64 | .cpu_arch = null, | |
| 65 | }; | |
| 66 | defer parser.args.deinit(gpa); | |
| 67 | ||
| 68 | parser.start() catch |err| switch (err) { | |
| 69 | error.UnexpectedToken => { | |
| 70 | const last_token_id = parser.it.pos - 1; | |
| 71 | const last_token = parser.it.get(last_token_id); | |
| 72 | const lcol = line_col.items[last_token_id]; | |
| 73 | return diags.failParse(path, "unexpected token in LD script: {s}: '{s}' ({d}:{d})", .{ | |
| 74 | @tagName(last_token.id), | |
| 75 | last_token.get(data), | |
| 76 | lcol.line, | |
| 77 | lcol.column, | |
| 78 | }); | |
| 79 | }, | |
| 80 | else => |e| return e, | |
| 81 | }; | |
| 82 | return .{ | |
| 83 | .path = path, | |
| 84 | .cpu_arch = parser.cpu_arch, | |
| 85 | .args = try parser.args.toOwnedSlice(gpa), | |
| 86 | }; | |
| 87 | } | |
| 88 | ||
| 89 | const LineColumn = struct { | |
| 90 | line: usize, | |
| 91 | column: usize, | |
| 92 | }; | |
| 93 | ||
| 94 | const Command = enum { | |
| 95 | output_format, | |
| 96 | input, | |
| 97 | group, | |
| 98 | as_needed, | |
| 99 | ||
| 100 | fn fromString(s: []const u8) ?Command { | |
| 101 | inline for (@typeInfo(Command).@"enum".fields) |field| { | |
| 102 | const upper_name = n: { | |
| 103 | comptime var buf: [field.name.len]u8 = undefined; | |
| 104 | inline for (field.name, 0..) |c, i| { | |
| 105 | buf[i] = comptime std.ascii.toUpper(c); | |
| 106 | } | |
| 107 | break :n buf; | |
| 108 | }; | |
| 109 | if (std.mem.eql(u8, &upper_name, s)) return @field(Command, field.name); | |
| 110 | } | |
| 111 | return null; | |
| 112 | } | |
| 113 | }; | |
| 114 | ||
| 115 | const Parser = struct { | |
| 116 | gpa: Allocator, | |
| 117 | source: []const u8, | |
| 118 | it: *TokenIterator, | |
| 119 | ||
| 120 | cpu_arch: ?std.Target.Cpu.Arch, | |
| 121 | args: std.ArrayListUnmanaged(Arg), | |
| 122 | ||
| 123 | fn start(parser: *Parser) !void { | |
| 124 | while (true) { | |
| 125 | parser.skipAny(&.{ .comment, .new_line }); | |
| 126 | ||
| 127 | if (parser.maybe(.command)) |cmd_id| { | |
| 128 | const cmd = parser.getCommand(cmd_id); | |
| 129 | switch (cmd) { | |
| 130 | .output_format => parser.cpu_arch = try parser.outputFormat(), | |
| 131 | // TODO we should verify that group only contains libraries | |
| 132 | .input, .group => try parser.group(), | |
| 133 | else => return error.UnexpectedToken, | |
| 134 | } | |
| 135 | } else break; | |
| 136 | } | |
| 137 | ||
| 138 | if (parser.it.next()) |tok| switch (tok.id) { | |
| 139 | .eof => {}, | |
| 140 | else => return error.UnexpectedToken, | |
| 141 | }; | |
| 142 | } | |
| 143 | ||
| 144 | fn outputFormat(p: *Parser) !std.Target.Cpu.Arch { | |
| 145 | const value = value: { | |
| 146 | if (p.skip(&.{.lparen})) { | |
| 147 | const value_id = try p.require(.literal); | |
| 148 | const value = p.it.get(value_id); | |
| 149 | _ = try p.require(.rparen); | |
| 150 | break :value value.get(p.source); | |
| 151 | } else if (p.skip(&.{ .new_line, .lbrace })) { | |
| 152 | const value_id = try p.require(.literal); | |
| 153 | const value = p.it.get(value_id); | |
| 154 | _ = p.skip(&.{.new_line}); | |
| 155 | _ = try p.require(.rbrace); | |
| 156 | break :value value.get(p.source); | |
| 157 | } else return error.UnexpectedToken; | |
| 158 | }; | |
| 159 | if (std.mem.eql(u8, value, "elf64-x86-64")) return .x86_64; | |
| 160 | if (std.mem.eql(u8, value, "elf64-littleaarch64")) return .aarch64; | |
| 161 | return error.UnknownCpuArch; | |
| 162 | } | |
| 163 | ||
| 164 | fn group(p: *Parser) !void { | |
| 165 | const gpa = p.gpa; | |
| 166 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | |
| 167 | ||
| 168 | while (true) { | |
| 169 | if (p.maybe(.literal)) |tok_id| { | |
| 170 | const tok = p.it.get(tok_id); | |
| 171 | const path = tok.get(p.source); | |
| 172 | try p.args.append(gpa, .{ .path = path, .needed = true }); | |
| 173 | } else if (p.maybe(.command)) |cmd_id| { | |
| 174 | const cmd = p.getCommand(cmd_id); | |
| 175 | switch (cmd) { | |
| 176 | .as_needed => try p.asNeeded(), | |
| 177 | else => return error.UnexpectedToken, | |
| 178 | } | |
| 179 | } else break; | |
| 180 | } | |
| 181 | ||
| 182 | _ = try p.require(.rparen); | |
| 183 | } | |
| 184 | ||
| 185 | fn asNeeded(p: *Parser) !void { | |
| 186 | const gpa = p.gpa; | |
| 187 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | |
| 188 | ||
| 189 | while (p.maybe(.literal)) |tok_id| { | |
| 190 | const tok = p.it.get(tok_id); | |
| 191 | const path = tok.get(p.source); | |
| 192 | try p.args.append(gpa, .{ .path = path, .needed = false }); | |
| 193 | } | |
| 194 | ||
| 195 | _ = try p.require(.rparen); | |
| 196 | } | |
| 197 | ||
| 198 | fn skip(p: *Parser, comptime ids: []const Token.Id) bool { | |
| 199 | const pos = p.it.pos; | |
| 200 | inline for (ids) |id| { | |
| 201 | const tok = p.it.next() orelse return false; | |
| 202 | if (tok.id != id) { | |
| 203 | p.it.seekTo(pos); | |
| 204 | return false; | |
| 205 | } | |
| 206 | } | |
| 207 | return true; | |
| 208 | } | |
| 209 | ||
| 210 | fn skipAny(p: *Parser, comptime ids: []const Token.Id) void { | |
| 211 | outer: while (p.it.next()) |tok| { | |
| 212 | inline for (ids) |id| { | |
| 213 | if (id == tok.id) continue :outer; | |
| 214 | } | |
| 215 | break p.it.seekBy(-1); | |
| 216 | } | |
| 217 | } | |
| 218 | ||
| 219 | fn maybe(p: *Parser, comptime id: Token.Id) ?Token.Index { | |
| 220 | const pos = p.it.pos; | |
| 221 | const tok = p.it.next() orelse return null; | |
| 222 | if (tok.id == id) return pos; | |
| 223 | p.it.seekBy(-1); | |
| 224 | return null; | |
| 225 | } | |
| 226 | ||
| 227 | fn require(p: *Parser, comptime id: Token.Id) !Token.Index { | |
| 228 | return p.maybe(id) orelse return error.UnexpectedToken; | |
| 229 | } | |
| 230 | ||
| 231 | fn getCommand(p: *Parser, index: Token.Index) Command { | |
| 232 | const tok = p.it.get(index); | |
| 233 | assert(tok.id == .command); | |
| 234 | return Command.fromString(tok.get(p.source)).?; | |
| 235 | } | |
| 236 | }; | |
| 237 | ||
| 238 | const Token = struct { | |
| 239 | id: Id, | |
| 240 | start: usize, | |
| 241 | end: usize, | |
| 242 | ||
| 243 | const Id = enum { | |
| 244 | eof, | |
| 245 | invalid, | |
| 246 | ||
| 247 | new_line, | |
| 248 | lparen, // ( | |
| 249 | rparen, // ) | |
| 250 | lbrace, // { | |
| 251 | rbrace, // } | |
| 252 | ||
| 253 | comment, // /* */ | |
| 254 | ||
| 255 | command, // literal with special meaning, see Command | |
| 256 | literal, | |
| 257 | }; | |
| 258 | ||
| 259 | const Index = usize; | |
| 260 | ||
| 261 | fn get(tok: Token, source: []const u8) []const u8 { | |
| 262 | return source[tok.start..tok.end]; | |
| 263 | } | |
| 264 | }; | |
| 265 | ||
| 266 | const Tokenizer = struct { | |
| 267 | source: []const u8, | |
| 268 | index: usize = 0, | |
| 269 | ||
| 270 | fn matchesPattern(comptime pattern: []const u8, slice: []const u8) bool { | |
| 271 | comptime var count: usize = 0; | |
| 272 | inline while (count < pattern.len) : (count += 1) { | |
| 273 | if (count >= slice.len) return false; | |
| 274 | const c = slice[count]; | |
| 275 | if (pattern[count] != c) return false; | |
| 276 | } | |
| 277 | return true; | |
| 278 | } | |
| 279 | ||
| 280 | fn matches(tok: Tokenizer, comptime pattern: []const u8) bool { | |
| 281 | return matchesPattern(pattern, tok.source[tok.index..]); | |
| 282 | } | |
| 283 | ||
| 284 | fn isCommand(tok: Tokenizer, start: usize, end: usize) bool { | |
| 285 | return if (Command.fromString(tok.source[start..end]) == null) false else true; | |
| 286 | } | |
| 287 | ||
| 288 | fn next(tok: *Tokenizer) Token { | |
| 289 | var result = Token{ | |
| 290 | .id = .eof, | |
| 291 | .start = tok.index, | |
| 292 | .end = undefined, | |
| 293 | }; | |
| 294 | ||
| 295 | var state: enum { | |
| 296 | start, | |
| 297 | comment, | |
| 298 | literal, | |
| 299 | } = .start; | |
| 300 | ||
| 301 | while (tok.index < tok.source.len) : (tok.index += 1) { | |
| 302 | const c = tok.source[tok.index]; | |
| 303 | switch (state) { | |
| 304 | .start => switch (c) { | |
| 305 | ' ', '\t' => result.start += 1, | |
| 306 | ||
| 307 | '\n' => { | |
| 308 | result.id = .new_line; | |
| 309 | tok.index += 1; | |
| 310 | break; | |
| 311 | }, | |
| 312 | ||
| 313 | '\r' => { | |
| 314 | if (tok.matches("\r\n")) { | |
| 315 | result.id = .new_line; | |
| 316 | tok.index += "\r\n".len; | |
| 317 | } else { | |
| 318 | result.id = .invalid; | |
| 319 | tok.index += 1; | |
| 320 | } | |
| 321 | break; | |
| 322 | }, | |
| 323 | ||
| 324 | '/' => if (tok.matches("/*")) { | |
| 325 | state = .comment; | |
| 326 | tok.index += "/*".len; | |
| 327 | } else { | |
| 328 | state = .literal; | |
| 329 | }, | |
| 330 | ||
| 331 | '(' => { | |
| 332 | result.id = .lparen; | |
| 333 | tok.index += 1; | |
| 334 | break; | |
| 335 | }, | |
| 336 | ||
| 337 | ')' => { | |
| 338 | result.id = .rparen; | |
| 339 | tok.index += 1; | |
| 340 | break; | |
| 341 | }, | |
| 342 | ||
| 343 | '{' => { | |
| 344 | result.id = .lbrace; | |
| 345 | tok.index += 1; | |
| 346 | break; | |
| 347 | }, | |
| 348 | ||
| 349 | '}' => { | |
| 350 | result.id = .rbrace; | |
| 351 | tok.index += 1; | |
| 352 | break; | |
| 353 | }, | |
| 354 | ||
| 355 | else => state = .literal, | |
| 356 | }, | |
| 357 | ||
| 358 | .comment => switch (c) { | |
| 359 | '*' => if (tok.matches("*/")) { | |
| 360 | result.id = .comment; | |
| 361 | tok.index += "*/".len; | |
| 362 | break; | |
| 363 | }, | |
| 364 | else => {}, | |
| 365 | }, | |
| 366 | ||
| 367 | .literal => switch (c) { | |
| 368 | ' ', '(', '\n' => { | |
| 369 | if (tok.isCommand(result.start, tok.index)) { | |
| 370 | result.id = .command; | |
| 371 | } else { | |
| 372 | result.id = .literal; | |
| 373 | } | |
| 374 | break; | |
| 375 | }, | |
| 376 | ||
| 377 | ')' => { | |
| 378 | result.id = .literal; | |
| 379 | break; | |
| 380 | }, | |
| 381 | ||
| 382 | '\r' => { | |
| 383 | if (tok.matches("\r\n")) { | |
| 384 | if (tok.isCommand(result.start, tok.index)) { | |
| 385 | result.id = .command; | |
| 386 | } else { | |
| 387 | result.id = .literal; | |
| 388 | } | |
| 389 | } else { | |
| 390 | result.id = .invalid; | |
| 391 | tok.index += 1; | |
| 392 | } | |
| 393 | break; | |
| 394 | }, | |
| 395 | ||
| 396 | else => {}, | |
| 397 | }, | |
| 398 | } | |
| 399 | } | |
| 400 | ||
| 401 | result.end = tok.index; | |
| 402 | return result; | |
| 403 | } | |
| 404 | }; | |
| 405 | ||
| 406 | const TokenIterator = struct { | |
| 407 | tokens: []const Token, | |
| 408 | pos: Token.Index = 0, | |
| 409 | ||
| 410 | fn next(it: *TokenIterator) ?Token { | |
| 411 | const token = it.peek() orelse return null; | |
| 412 | it.pos += 1; | |
| 413 | return token; | |
| 414 | } | |
| 415 | ||
| 416 | fn peek(it: TokenIterator) ?Token { | |
| 417 | if (it.pos >= it.tokens.len) return null; | |
| 418 | return it.tokens[it.pos]; | |
| 419 | } | |
| 420 | ||
| 421 | fn reset(it: *TokenIterator) void { | |
| 422 | it.pos = 0; | |
| 423 | } | |
| 424 | ||
| 425 | fn seekTo(it: *TokenIterator, pos: Token.Index) void { | |
| 426 | it.pos = pos; | |
| 427 | } | |
| 428 | ||
| 429 | fn seekBy(it: *TokenIterator, offset: isize) void { | |
| 430 | const new_pos = @as(isize, @bitCast(it.pos)) + offset; | |
| 431 | if (new_pos < 0) { | |
| 432 | it.pos = 0; | |
| 433 | } else { | |
| 434 | it.pos = @as(usize, @intCast(new_pos)); | |
| 435 | } | |
| 436 | } | |
| 437 | ||
| 438 | fn get(it: *TokenIterator, pos: Token.Index) Token { | |
| 439 | assert(pos < it.tokens.len); | |
| 440 | return it.tokens[pos]; | |
| 441 | } | |
| 442 | }; | |
| 443 | ||
| 444 | const LdScript = @This(); | |
| 445 | const Diags = @import("../../link.zig").Diags; | |
| 446 | ||
| 447 | const std = @import("std"); | |
| 448 | const assert = std.debug.assert; | |
| 449 | const Path = std.Build.Cache.Path; | |
| 450 | const Allocator = std.mem.Allocator; |
src/link/LdScript.zig created+450| ... | ... | @@ -0,0 +1,450 @@ |
| 1 | path: Path, | |
| 2 | cpu_arch: ?std.Target.Cpu.Arch, | |
| 3 | args: []const Arg, | |
| 4 | ||
| 5 | pub const Arg = struct { | |
| 6 | needed: bool = false, | |
| 7 | path: []const u8, | |
| 8 | }; | |
| 9 | ||
| 10 | pub fn deinit(ls: *LdScript, gpa: Allocator) void { | |
| 11 | gpa.free(ls.args); | |
| 12 | ls.* = undefined; | |
| 13 | } | |
| 14 | ||
| 15 | pub const Error = error{ | |
| 16 | LinkFailure, | |
| 17 | UnexpectedToken, | |
| 18 | UnknownCpuArch, | |
| 19 | OutOfMemory, | |
| 20 | }; | |
| 21 | ||
| 22 | pub fn parse( | |
| 23 | gpa: Allocator, | |
| 24 | diags: *Diags, | |
| 25 | /// For error reporting. | |
| 26 | path: Path, | |
| 27 | data: []const u8, | |
| 28 | ) Error!LdScript { | |
| 29 | var tokenizer = Tokenizer{ .source = data }; | |
| 30 | var tokens: std.ArrayListUnmanaged(Token) = .empty; | |
| 31 | defer tokens.deinit(gpa); | |
| 32 | var line_col: std.ArrayListUnmanaged(LineColumn) = .empty; | |
| 33 | defer line_col.deinit(gpa); | |
| 34 | ||
| 35 | var line: usize = 0; | |
| 36 | var prev_line_last_col: usize = 0; | |
| 37 | ||
| 38 | while (true) { | |
| 39 | const tok = tokenizer.next(); | |
| 40 | try tokens.append(gpa, tok); | |
| 41 | const column = tok.start - prev_line_last_col; | |
| 42 | try line_col.append(gpa, .{ .line = line, .column = column }); | |
| 43 | switch (tok.id) { | |
| 44 | .invalid => { | |
| 45 | return diags.failParse(path, "invalid token in LD script: '{s}' ({d}:{d})", .{ | |
| 46 | std.fmt.fmtSliceEscapeLower(tok.get(data)), line, column, | |
| 47 | }); | |
| 48 | }, | |
| 49 | .new_line => { | |
| 50 | line += 1; | |
| 51 | prev_line_last_col = tok.end; | |
| 52 | }, | |
| 53 | .eof => break, | |
| 54 | else => {}, | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | var it: TokenIterator = .{ .tokens = tokens.items }; | |
| 59 | var parser: Parser = .{ | |
| 60 | .gpa = gpa, | |
| 61 | .source = data, | |
| 62 | .it = &it, | |
| 63 | .args = .empty, | |
| 64 | .cpu_arch = null, | |
| 65 | }; | |
| 66 | defer parser.args.deinit(gpa); | |
| 67 | ||
| 68 | parser.start() catch |err| switch (err) { | |
| 69 | error.UnexpectedToken => { | |
| 70 | const last_token_id = parser.it.pos - 1; | |
| 71 | const last_token = parser.it.get(last_token_id); | |
| 72 | const lcol = line_col.items[last_token_id]; | |
| 73 | return diags.failParse(path, "unexpected token in LD script: {s}: '{s}' ({d}:{d})", .{ | |
| 74 | @tagName(last_token.id), | |
| 75 | last_token.get(data), | |
| 76 | lcol.line, | |
| 77 | lcol.column, | |
| 78 | }); | |
| 79 | }, | |
| 80 | else => |e| return e, | |
| 81 | }; | |
| 82 | return .{ | |
| 83 | .path = path, | |
| 84 | .cpu_arch = parser.cpu_arch, | |
| 85 | .args = try parser.args.toOwnedSlice(gpa), | |
| 86 | }; | |
| 87 | } | |
| 88 | ||
| 89 | const LineColumn = struct { | |
| 90 | line: usize, | |
| 91 | column: usize, | |
| 92 | }; | |
| 93 | ||
| 94 | const Command = enum { | |
| 95 | output_format, | |
| 96 | input, | |
| 97 | group, | |
| 98 | as_needed, | |
| 99 | ||
| 100 | fn fromString(s: []const u8) ?Command { | |
| 101 | inline for (@typeInfo(Command).@"enum".fields) |field| { | |
| 102 | const upper_name = n: { | |
| 103 | comptime var buf: [field.name.len]u8 = undefined; | |
| 104 | inline for (field.name, 0..) |c, i| { | |
| 105 | buf[i] = comptime std.ascii.toUpper(c); | |
| 106 | } | |
| 107 | break :n buf; | |
| 108 | }; | |
| 109 | if (std.mem.eql(u8, &upper_name, s)) return @field(Command, field.name); | |
| 110 | } | |
| 111 | return null; | |
| 112 | } | |
| 113 | }; | |
| 114 | ||
| 115 | const Parser = struct { | |
| 116 | gpa: Allocator, | |
| 117 | source: []const u8, | |
| 118 | it: *TokenIterator, | |
| 119 | ||
| 120 | cpu_arch: ?std.Target.Cpu.Arch, | |
| 121 | args: std.ArrayListUnmanaged(Arg), | |
| 122 | ||
| 123 | fn start(parser: *Parser) !void { | |
| 124 | while (true) { | |
| 125 | parser.skipAny(&.{ .comment, .new_line }); | |
| 126 | ||
| 127 | if (parser.maybe(.command)) |cmd_id| { | |
| 128 | const cmd = parser.getCommand(cmd_id); | |
| 129 | switch (cmd) { | |
| 130 | .output_format => parser.cpu_arch = try parser.outputFormat(), | |
| 131 | // TODO we should verify that group only contains libraries | |
| 132 | .input, .group => try parser.group(), | |
| 133 | else => return error.UnexpectedToken, | |
| 134 | } | |
| 135 | } else break; | |
| 136 | } | |
| 137 | ||
| 138 | if (parser.it.next()) |tok| switch (tok.id) { | |
| 139 | .eof => {}, | |
| 140 | else => return error.UnexpectedToken, | |
| 141 | }; | |
| 142 | } | |
| 143 | ||
| 144 | fn outputFormat(p: *Parser) !std.Target.Cpu.Arch { | |
| 145 | const value = value: { | |
| 146 | if (p.skip(&.{.lparen})) { | |
| 147 | const value_id = try p.require(.literal); | |
| 148 | const value = p.it.get(value_id); | |
| 149 | _ = try p.require(.rparen); | |
| 150 | break :value value.get(p.source); | |
| 151 | } else if (p.skip(&.{ .new_line, .lbrace })) { | |
| 152 | const value_id = try p.require(.literal); | |
| 153 | const value = p.it.get(value_id); | |
| 154 | _ = p.skip(&.{.new_line}); | |
| 155 | _ = try p.require(.rbrace); | |
| 156 | break :value value.get(p.source); | |
| 157 | } else return error.UnexpectedToken; | |
| 158 | }; | |
| 159 | if (std.mem.eql(u8, value, "elf64-x86-64")) return .x86_64; | |
| 160 | if (std.mem.eql(u8, value, "elf64-littleaarch64")) return .aarch64; | |
| 161 | return error.UnknownCpuArch; | |
| 162 | } | |
| 163 | ||
| 164 | fn group(p: *Parser) !void { | |
| 165 | const gpa = p.gpa; | |
| 166 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | |
| 167 | ||
| 168 | while (true) { | |
| 169 | if (p.maybe(.literal)) |tok_id| { | |
| 170 | const tok = p.it.get(tok_id); | |
| 171 | const path = tok.get(p.source); | |
| 172 | try p.args.append(gpa, .{ .path = path, .needed = true }); | |
| 173 | } else if (p.maybe(.command)) |cmd_id| { | |
| 174 | const cmd = p.getCommand(cmd_id); | |
| 175 | switch (cmd) { | |
| 176 | .as_needed => try p.asNeeded(), | |
| 177 | else => return error.UnexpectedToken, | |
| 178 | } | |
| 179 | } else break; | |
| 180 | } | |
| 181 | ||
| 182 | _ = try p.require(.rparen); | |
| 183 | } | |
| 184 | ||
| 185 | fn asNeeded(p: *Parser) !void { | |
| 186 | const gpa = p.gpa; | |
| 187 | if (!p.skip(&.{.lparen})) return error.UnexpectedToken; | |
| 188 | ||
| 189 | while (p.maybe(.literal)) |tok_id| { | |
| 190 | const tok = p.it.get(tok_id); | |
| 191 | const path = tok.get(p.source); | |
| 192 | try p.args.append(gpa, .{ .path = path, .needed = false }); | |
| 193 | } | |
| 194 | ||
| 195 | _ = try p.require(.rparen); | |
| 196 | } | |
| 197 | ||
| 198 | fn skip(p: *Parser, comptime ids: []const Token.Id) bool { | |
| 199 | const pos = p.it.pos; | |
| 200 | inline for (ids) |id| { | |
| 201 | const tok = p.it.next() orelse return false; | |
| 202 | if (tok.id != id) { | |
| 203 | p.it.seekTo(pos); | |
| 204 | return false; | |
| 205 | } | |
| 206 | } | |
| 207 | return true; | |
| 208 | } | |
| 209 | ||
| 210 | fn skipAny(p: *Parser, comptime ids: []const Token.Id) void { | |
| 211 | outer: while (p.it.next()) |tok| { | |
| 212 | inline for (ids) |id| { | |
| 213 | if (id == tok.id) continue :outer; | |
| 214 | } | |
| 215 | break p.it.seekBy(-1); | |
| 216 | } | |
| 217 | } | |
| 218 | ||
| 219 | fn maybe(p: *Parser, comptime id: Token.Id) ?Token.Index { | |
| 220 | const pos = p.it.pos; | |
| 221 | const tok = p.it.next() orelse return null; | |
| 222 | if (tok.id == id) return pos; | |
| 223 | p.it.seekBy(-1); | |
| 224 | return null; | |
| 225 | } | |
| 226 | ||
| 227 | fn require(p: *Parser, comptime id: Token.Id) !Token.Index { | |
| 228 | return p.maybe(id) orelse return error.UnexpectedToken; | |
| 229 | } | |
| 230 | ||
| 231 | fn getCommand(p: *Parser, index: Token.Index) Command { | |
| 232 | const tok = p.it.get(index); | |
| 233 | assert(tok.id == .command); | |
| 234 | return Command.fromString(tok.get(p.source)).?; | |
| 235 | } | |
| 236 | }; | |
| 237 | ||
| 238 | const Token = struct { | |
| 239 | id: Id, | |
| 240 | start: usize, | |
| 241 | end: usize, | |
| 242 | ||
| 243 | const Id = enum { | |
| 244 | eof, | |
| 245 | invalid, | |
| 246 | ||
| 247 | new_line, | |
| 248 | lparen, // ( | |
| 249 | rparen, // ) | |
| 250 | lbrace, // { | |
| 251 | rbrace, // } | |
| 252 | ||
| 253 | comment, // /* */ | |
| 254 | ||
| 255 | command, // literal with special meaning, see Command | |
| 256 | literal, | |
| 257 | }; | |
| 258 | ||
| 259 | const Index = usize; | |
| 260 | ||
| 261 | fn get(tok: Token, source: []const u8) []const u8 { | |
| 262 | return source[tok.start..tok.end]; | |
| 263 | } | |
| 264 | }; | |
| 265 | ||
| 266 | const Tokenizer = struct { | |
| 267 | source: []const u8, | |
| 268 | index: usize = 0, | |
| 269 | ||
| 270 | fn matchesPattern(comptime pattern: []const u8, slice: []const u8) bool { | |
| 271 | comptime var count: usize = 0; | |
| 272 | inline while (count < pattern.len) : (count += 1) { | |
| 273 | if (count >= slice.len) return false; | |
| 274 | const c = slice[count]; | |
| 275 | if (pattern[count] != c) return false; | |
| 276 | } | |
| 277 | return true; | |
| 278 | } | |
| 279 | ||
| 280 | fn matches(tok: Tokenizer, comptime pattern: []const u8) bool { | |
| 281 | return matchesPattern(pattern, tok.source[tok.index..]); | |
| 282 | } | |
| 283 | ||
| 284 | fn isCommand(tok: Tokenizer, start: usize, end: usize) bool { | |
| 285 | return if (Command.fromString(tok.source[start..end]) == null) false else true; | |
| 286 | } | |
| 287 | ||
| 288 | fn next(tok: *Tokenizer) Token { | |
| 289 | var result = Token{ | |
| 290 | .id = .eof, | |
| 291 | .start = tok.index, | |
| 292 | .end = undefined, | |
| 293 | }; | |
| 294 | ||
| 295 | var state: enum { | |
| 296 | start, | |
| 297 | comment, | |
| 298 | literal, | |
| 299 | } = .start; | |
| 300 | ||
| 301 | while (tok.index < tok.source.len) : (tok.index += 1) { | |
| 302 | const c = tok.source[tok.index]; | |
| 303 | switch (state) { | |
| 304 | .start => switch (c) { | |
| 305 | ' ', '\t' => result.start += 1, | |
| 306 | ||
| 307 | '\n' => { | |
| 308 | result.id = .new_line; | |
| 309 | tok.index += 1; | |
| 310 | break; | |
| 311 | }, | |
| 312 | ||
| 313 | '\r' => { | |
| 314 | if (tok.matches("\r\n")) { | |
| 315 | result.id = .new_line; | |
| 316 | tok.index += "\r\n".len; | |
| 317 | } else { | |
| 318 | result.id = .invalid; | |
| 319 | tok.index += 1; | |
| 320 | } | |
| 321 | break; | |
| 322 | }, | |
| 323 | ||
| 324 | '/' => if (tok.matches("/*")) { | |
| 325 | state = .comment; | |
| 326 | tok.index += "/*".len; | |
| 327 | } else { | |
| 328 | state = .literal; | |
| 329 | }, | |
| 330 | ||
| 331 | '(' => { | |
| 332 | result.id = .lparen; | |
| 333 | tok.index += 1; | |
| 334 | break; | |
| 335 | }, | |
| 336 | ||
| 337 | ')' => { | |
| 338 | result.id = .rparen; | |
| 339 | tok.index += 1; | |
| 340 | break; | |
| 341 | }, | |
| 342 | ||
| 343 | '{' => { | |
| 344 | result.id = .lbrace; | |
| 345 | tok.index += 1; | |
| 346 | break; | |
| 347 | }, | |
| 348 | ||
| 349 | '}' => { | |
| 350 | result.id = .rbrace; | |
| 351 | tok.index += 1; | |
| 352 | break; | |
| 353 | }, | |
| 354 | ||
| 355 | else => state = .literal, | |
| 356 | }, | |
| 357 | ||
| 358 | .comment => switch (c) { | |
| 359 | '*' => if (tok.matches("*/")) { | |
| 360 | result.id = .comment; | |
| 361 | tok.index += "*/".len; | |
| 362 | break; | |
| 363 | }, | |
| 364 | else => {}, | |
| 365 | }, | |
| 366 | ||
| 367 | .literal => switch (c) { | |
| 368 | ' ', '(', '\n' => { | |
| 369 | if (tok.isCommand(result.start, tok.index)) { | |
| 370 | result.id = .command; | |
| 371 | } else { | |
| 372 | result.id = .literal; | |
| 373 | } | |
| 374 | break; | |
| 375 | }, | |
| 376 | ||
| 377 | ')' => { | |
| 378 | result.id = .literal; | |
| 379 | break; | |
| 380 | }, | |
| 381 | ||
| 382 | '\r' => { | |
| 383 | if (tok.matches("\r\n")) { | |
| 384 | if (tok.isCommand(result.start, tok.index)) { | |
| 385 | result.id = .command; | |
| 386 | } else { | |
| 387 | result.id = .literal; | |
| 388 | } | |
| 389 | } else { | |
| 390 | result.id = .invalid; | |
| 391 | tok.index += 1; | |
| 392 | } | |
| 393 | break; | |
| 394 | }, | |
| 395 | ||
| 396 | else => {}, | |
| 397 | }, | |
| 398 | } | |
| 399 | } | |
| 400 | ||
| 401 | result.end = tok.index; | |
| 402 | return result; | |
| 403 | } | |
| 404 | }; | |
| 405 | ||
| 406 | const TokenIterator = struct { | |
| 407 | tokens: []const Token, | |
| 408 | pos: Token.Index = 0, | |
| 409 | ||
| 410 | fn next(it: *TokenIterator) ?Token { | |
| 411 | const token = it.peek() orelse return null; | |
| 412 | it.pos += 1; | |
| 413 | return token; | |
| 414 | } | |
| 415 | ||
| 416 | fn peek(it: TokenIterator) ?Token { | |
| 417 | if (it.pos >= it.tokens.len) return null; | |
| 418 | return it.tokens[it.pos]; | |
| 419 | } | |
| 420 | ||
| 421 | fn reset(it: *TokenIterator) void { | |
| 422 | it.pos = 0; | |
| 423 | } | |
| 424 | ||
| 425 | fn seekTo(it: *TokenIterator, pos: Token.Index) void { | |
| 426 | it.pos = pos; | |
| 427 | } | |
| 428 | ||
| 429 | fn seekBy(it: *TokenIterator, offset: isize) void { | |
| 430 | const new_pos = @as(isize, @bitCast(it.pos)) + offset; | |
| 431 | if (new_pos < 0) { | |
| 432 | it.pos = 0; | |
| 433 | } else { | |
| 434 | it.pos = @as(usize, @intCast(new_pos)); | |
| 435 | } | |
| 436 | } | |
| 437 | ||
| 438 | fn get(it: *TokenIterator, pos: Token.Index) Token { | |
| 439 | assert(pos < it.tokens.len); | |
| 440 | return it.tokens[pos]; | |
| 441 | } | |
| 442 | }; | |
| 443 | ||
| 444 | const LdScript = @This(); | |
| 445 | const Diags = @import("../link.zig").Diags; | |
| 446 | ||
| 447 | const std = @import("std"); | |
| 448 | const assert = std.debug.assert; | |
| 449 | const Path = std.Build.Cache.Path; | |
| 450 | const Allocator = std.mem.Allocator; |