| 1 | //! Air -> Js.Tree |
| 2 | gpa: Allocator, |
| 3 | arena: Allocator, |
| 4 | air: Air, |
| 5 | ip: *InternPool, |
| 6 | |
| 7 | nodes: Node.List, |
| 8 | extra: dod.ExtraStore(.js_tree, .@"var"), |
| 9 | extern_symbols: std.AutoArrayHashMapUnmanaged(InternPool.String.WithLen, void), |
| 10 | top_level_value_deps: std.AutoArrayHashMapUnmanaged(Value, void), |
| 11 | |
| 12 | param_count: u32, |
| 13 | air_node_map: []MapEntry, |
| 14 | scratch: std.ArrayListUnmanaged(u32), |
| 15 | next_id: u32, |
| 16 | next_label: u32, |
| 17 | |
| 18 | const Error = error{ |
| 19 | OutOfMemory, |
| 20 | }; |
| 21 | |
| 22 | const MapEntry = union { |
| 23 | none: void, |
| 24 | node: Node.Index, |
| 25 | block: packed struct { |
| 26 | used: bool = false, |
| 27 | index: u31, |
| 28 | }, |
| 29 | }; |
| 30 | |
| 31 | pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: Air) !NodeGen { |
| 32 | const t = tracer.trace(@src()); |
| 33 | defer t.end(); |
| 34 | |
| 35 | const air_node_map = try arena.alloc(MapEntry, air.insts.len); |
| 36 | if (core.runtime_safety) { |
| 37 | @memset(air_node_map, .{ .none = {} }); |
| 38 | } |
| 39 | const param_count = air.paramCount(); |
| 40 | return .{ |
| 41 | .gpa = gpa, |
| 42 | .arena = arena, |
| 43 | .air = air, |
| 44 | .ip = ip, |
| 45 | .param_count = param_count, |
| 46 | .nodes = .{}, |
| 47 | .extern_symbols = .{}, |
| 48 | .top_level_value_deps = .{}, |
| 49 | .air_node_map = air_node_map, |
| 50 | .extra = .empty, |
| 51 | .scratch = .empty, |
| 52 | .next_id = param_count, |
| 53 | .next_label = 0, |
| 54 | }; |
| 55 | } |
| 56 | |
| 57 | pub fn deinit(c: *NodeGen) void { |
| 58 | const t = tracer.trace(@src()); |
| 59 | defer t.end(); |
| 60 | |
| 61 | defer c.* = undefined; |
| 62 | c.nodes.deinit(c.gpa); |
| 63 | c.extra.deinit(c.gpa); |
| 64 | c.extern_symbols.deinit(c.gpa); |
| 65 | c.top_level_value_deps.deinit(c.gpa); |
| 66 | |
| 67 | // arena can deinit the rest |
| 68 | } |
| 69 | |
| 70 | pub fn finalize(c: *NodeGen) !Tree { |
| 71 | const t = tracer.trace(@src()); |
| 72 | defer t.end(); |
| 73 | |
| 74 | // todo: errdefer will probably leave memory in a fully invalid state |
| 75 | try c.nodes.setCapacity(c.gpa, c.nodes.len); |
| 76 | |
| 77 | var extra = try c.extra.toOwnedStore(c.gpa); |
| 78 | errdefer extra.deinit(c.gpa); |
| 79 | |
| 80 | // todo: i dont like this dupe |
| 81 | const extern_symbols = try c.gpa.dupe(InternPool.String.WithLen, c.extern_symbols.keys()); |
| 82 | errdefer c.gpa.free(extern_symbols); |
| 83 | |
| 84 | const top_level_value_deps = try c.gpa.dupe(Value, c.top_level_value_deps.keys()); |
| 85 | errdefer c.gpa.free(top_level_value_deps); |
| 86 | |
| 87 | defer c.deinit(); |
| 88 | |
| 89 | return .{ |
| 90 | .param_count = c.param_count, |
| 91 | .nodes = c.nodes.toOwnedSlice(), |
| 92 | .extern_symbols = extern_symbols, |
| 93 | .top_level_value_deps = top_level_value_deps, |
| 94 | .extra = extra, |
| 95 | }; |
| 96 | } |
| 97 | |
| 98 | pub fn build(c: *NodeGen) !void { |
| 99 | const t = tracer.trace(@src()); |
| 100 | defer t.end(); |
| 101 | |
| 102 | assert(c.air.instTag(.main_block) == .block); // expected Air to start with block |
| 103 | const statements = c.air.instData(.block, .main_block).get(&c.air); |
| 104 | |
| 105 | const scratch_top = c.scratch.items.len; |
| 106 | defer c.scratch.shrinkRetainingCapacity(scratch_top); |
| 107 | |
| 108 | _ = try c.add(.root, undefined); |
| 109 | |
| 110 | if (statements.len > 0) { |
| 111 | for (statements[0..c.param_count], 1..) |stmt, expected_id| { |
| 112 | assert(c.air.instTag(stmt) == .arg); // expected Air to start with param nodes |
| 113 | assert(@intFromEnum(stmt) == expected_id); |
| 114 | c.air_node_map[@intFromEnum(stmt)] = .{ .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }) }; |
| 115 | } |
| 116 | |
| 117 | for (statements[c.param_count..]) |stmt| { |
| 118 | try c.codeGenInst(stmt); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | const items = c.scratch.items[scratch_top..]; |
| 123 | const top = c.extra.list.items.len; |
| 124 | try c.extra.list.appendSlice(c.gpa, items); |
| 125 | c.nodes.items(.data)[0] = .encode(.root, .{ |
| 126 | .start = @intCast(top), |
| 127 | .len = @intCast(items.len), |
| 128 | }); |
| 129 | } |
| 130 | |
| 131 | fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 132 | const t = tracer.traceFmt(@src(), "#{d} {s}", .{ @intFromEnum(inst), @tagName(c.air.instTag(inst)) }); |
| 133 | defer t.end(); |
| 134 | |
| 135 | switch (c.air.instTag(inst)) { |
| 136 | .arg => unreachable, // codegen must skip over these instructions. |
| 137 | |
| 138 | .block => try c.codeGenBlock(inst), |
| 139 | |
| 140 | .memory => { |
| 141 | const i = @intFromEnum(inst); |
| 142 | const data: Air.Memory = c.air.instData(.memory, inst); |
| 143 | const value: Tree.Node.Index.Optional = if (data.init.isVoid()) |
| 144 | .none |
| 145 | else |
| 146 | (try c.fetchExpression(data.init)).toOptional(); |
| 147 | |
| 148 | try c.addStmt(try c.add(.stmt_declare_local, .{ |
| 149 | .identifier_index = c.next_id, |
| 150 | .init = value, |
| 151 | })); |
| 152 | const id = try c.add(.local, .{ .id = c.next_id }); |
| 153 | c.next_id += 1; |
| 154 | c.air_node_map[i] = .{ .node = id }; |
| 155 | }, |
| 156 | |
| 157 | inline .gt, |
| 158 | .lt, |
| 159 | .eq, |
| 160 | .ge, |
| 161 | .le, |
| 162 | .add, |
| 163 | .sub, |
| 164 | .mul, |
| 165 | .div, |
| 166 | => |tag| { |
| 167 | const data: Air.Bin = c.air.instData(tag, inst); |
| 168 | const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{ |
| 169 | .lhs = try c.fetchExpression(data.lhs), |
| 170 | .rhs = try c.fetchExpression(data.rhs), |
| 171 | }); |
| 172 | try c.markExpression(inst, node); |
| 173 | }, |
| 174 | |
| 175 | .field_array => { |
| 176 | const data: Air.Bin = c.air.instData(.field_array, inst); |
| 177 | const node = try c.add(.index, .{ |
| 178 | .lhs = try c.fetchExpression(data.lhs), |
| 179 | .rhs = try c.fetchExpression(data.rhs), |
| 180 | }); |
| 181 | try c.markExpression(inst, node); |
| 182 | }, |
| 183 | |
| 184 | .store => { |
| 185 | const data: Air.Store = c.air.instData(.store, inst); |
| 186 | |
| 187 | const target = try c.fetchInstExpression(data.memory); |
| 188 | |
| 189 | if (data.operand.kind == .index) { |
| 190 | const index = data.operand.decode().index; |
| 191 | switch (c.air.instTag(index)) { |
| 192 | inline .add, |
| 193 | .sub, |
| 194 | .mul, |
| 195 | .div, |
| 196 | => |tag| { |
| 197 | const inner_data: Air.Bin = c.air.instData(tag, index); |
| 198 | if (inner_data.lhs.eql(data.memory.toRef())) { |
| 199 | if ((tag == .add or tag == .sub) and |
| 200 | inner_data.rhs.kind == .value) |
| 201 | { |
| 202 | // if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| { |
| 203 | // if (f == 1) { |
| 204 | // // convert this into ++ or -- |
| 205 | // const node = try c.add( |
| 206 | // if (tag == .add) |
| 207 | // .pre_inc |
| 208 | // else |
| 209 | // .pre_dec, |
| 210 | // .{ |
| 211 | // .operand = target, |
| 212 | // }, |
| 213 | // ); |
| 214 | // try c.markExpression(inst, node); |
| 215 | |
| 216 | // return; |
| 217 | // } |
| 218 | // } |
| 219 | } |
| 220 | |
| 221 | // convert into a compound assignment |
| 222 | const node = try c.add( |
| 223 | @field(Tree.Tag, "assign_" ++ @tagName(tag)), |
| 224 | .{ |
| 225 | .lhs = target, |
| 226 | .rhs = try c.fetchExpression(inner_data.rhs), |
| 227 | }, |
| 228 | ); |
| 229 | try c.markExpression(inst, node); |
| 230 | |
| 231 | return; |
| 232 | } |
| 233 | }, |
| 234 | else => {}, |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | const node = try c.add(.assign, .{ |
| 239 | .lhs = target, |
| 240 | .rhs = try c.fetchExpression(data.operand), |
| 241 | }); |
| 242 | try c.markExpression(inst, node); |
| 243 | }, |
| 244 | |
| 245 | inline .negate, |
| 246 | .bool_not, |
| 247 | => |tag| { |
| 248 | const data: Air.Un = c.air.instData(tag, inst); |
| 249 | const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{ |
| 250 | .operand = try c.fetchExpression(data.operand), |
| 251 | }); |
| 252 | try c.markExpression(inst, node); |
| 253 | }, |
| 254 | |
| 255 | .panic => { |
| 256 | const data: Air.Un = c.air.instData(.panic, inst); |
| 257 | const string = try c.fetchExpression(data.operand); |
| 258 | |
| 259 | const error_constructor_string = (try c.ip.getOrPutString(c.gpa, "Error", .maybe_embedded_nulls)).withLen("Error".len); |
| 260 | try c.extern_symbols.put(c.gpa, error_constructor_string, {}); |
| 261 | |
| 262 | try c.extra.list.ensureUnusedCapacity(c.gpa, 1); |
| 263 | const start = c.extra.list.items.len; |
| 264 | c.extra.list.appendAssumeCapacity(@intFromEnum(string)); |
| 265 | |
| 266 | try c.addStmt(try c.add(.stmt_throw, .{ |
| 267 | .operand = try c.add(.js_new, .{ |
| 268 | .base = try c.add(.global, .{ .name = error_constructor_string }), |
| 269 | .args = try c.extra.append(c.gpa, Tree.IndexList, .{ |
| 270 | .start = @intCast(start), |
| 271 | .len = 1, |
| 272 | }), |
| 273 | }), |
| 274 | })); |
| 275 | }, |
| 276 | |
| 277 | .field_opaque => { |
| 278 | const data: Air.Bin = c.air.instData(.field_opaque, inst); |
| 279 | if (data.rhs.value()) |field_value| { |
| 280 | if (field_value.typeOf(c.ip) == .string) { |
| 281 | const node = try c.add(.field, .{ |
| 282 | .base = try c.fetchExpression(data.lhs), |
| 283 | .field = field_value.asString(c.ip).tryToNullTerminatedString(c.ip, c.gpa) catch @panic("TODO"), |
| 284 | }); |
| 285 | try c.markExpression(inst, node); |
| 286 | return; |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | const node = try c.add(.index, .{ |
| 291 | .lhs = try c.fetchExpression(data.lhs), |
| 292 | .rhs = try c.fetchExpression(data.rhs), |
| 293 | }); |
| 294 | try c.markExpression(inst, node); |
| 295 | }, |
| 296 | |
| 297 | .field_struct => { |
| 298 | const data: Air.FieldStruct = c.air.instData(.field_struct, inst); |
| 299 | const node = try c.add(.field, .{ |
| 300 | .base = try c.fetchInstExpression(data.base), |
| 301 | .field = field_name: { |
| 302 | break :field_name c.air.typeOfIndex(data.base, c.ip) |
| 303 | .toStructType(c.ip).? |
| 304 | .field_keys.at(c.ip, data.field_index); |
| 305 | }, |
| 306 | }); |
| 307 | try c.markExpression(inst, node); |
| 308 | }, |
| 309 | |
| 310 | .field_tuple => { |
| 311 | const data: Air.FieldStruct = c.air.instData(.field_tuple, inst); |
| 312 | const node = try c.add(.index, .{ |
| 313 | .lhs = try c.fetchInstExpression(data.base), |
| 314 | .rhs = try c.makeValueInst(.{ .int32 = @intCast(data.field_index) }), |
| 315 | }); |
| 316 | try c.markExpression(inst, node); |
| 317 | }, |
| 318 | |
| 319 | .array_len => { |
| 320 | const data: Air.Un = c.air.instData(.array_len, inst); |
| 321 | const node = try c.add(.field, .{ |
| 322 | .base = try c.fetchInstExpression( |
| 323 | data.operand.index() orelse |
| 324 | unreachable, // TODO: make a second version of Un |
| 325 | ), |
| 326 | .field = try c.ip.getOrPutString(c.gpa, "length", .no_embedded_nulls), |
| 327 | }); |
| 328 | try c.markExpression(inst, node); |
| 329 | }, |
| 330 | |
| 331 | inline .call, .js_new => |tag| { |
| 332 | const data: Air.Call = c.air.instData(tag, inst); |
| 333 | const call_data = data.call_data.get(&c.air); |
| 334 | |
| 335 | try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len); |
| 336 | const start = c.extra.list.items.len; |
| 337 | c.extra.list.items.len += call_data.args.len; |
| 338 | |
| 339 | const items = c.extra.list.items[start..][0..call_data.args.len]; |
| 340 | |
| 341 | for (call_data.args.get(&c.air), items) |arg, *dest| { |
| 342 | dest.* = @intFromEnum(try c.fetchExpression(arg)); |
| 343 | } |
| 344 | |
| 345 | const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{ |
| 346 | .base = try c.fetchExpression(data.base), |
| 347 | .args = try c.extra.append(c.gpa, Tree.IndexList, .{ |
| 348 | .start = @intCast(start), |
| 349 | .len = call_data.args.len, |
| 350 | }), |
| 351 | }); |
| 352 | try c.markExpression(inst, node); |
| 353 | }, |
| 354 | |
| 355 | .@"if" => { |
| 356 | const data: Air.If = c.air.instData(.@"if", inst); |
| 357 | const extra = data.data.get(&c.air); |
| 358 | var cond = try c.fetchExpression(data.cond); |
| 359 | |
| 360 | if (c.canLowerToExpression(extra.yes) and |
| 361 | c.canLowerToExpression(extra.no)) |
| 362 | { |
| 363 | if (extra.no.value()) |no_value| { |
| 364 | if (no_value == .bool_false) { |
| 365 | const yes = try c.lowerToExpression(extra.yes); |
| 366 | |
| 367 | const conditional = try c.add(.@"and", .{ |
| 368 | .lhs = cond, |
| 369 | .rhs = yes, |
| 370 | }); |
| 371 | try c.markExpression(inst, conditional); |
| 372 | return; |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | if (extra.yes.value()) |yes_value| { |
| 377 | if (yes_value == .bool_true) { |
| 378 | const no = try c.lowerToExpression(extra.no); |
| 379 | |
| 380 | const conditional = try c.add(.@"or", .{ |
| 381 | .lhs = cond, |
| 382 | .rhs = no, |
| 383 | }); |
| 384 | try c.markExpression(inst, conditional); |
| 385 | return; |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | const yes = try c.lowerToExpression(extra.yes); |
| 390 | const no = try c.lowerToExpression(extra.no); |
| 391 | |
| 392 | const ternary = try c.add(.ternary, .{ |
| 393 | .cond = cond, |
| 394 | .data = try c.extra.append(c.gpa, Tree.Ternary.Data, .{ |
| 395 | .yes = yes, |
| 396 | .no = no, |
| 397 | }), |
| 398 | }); |
| 399 | |
| 400 | try c.markExpression(inst, ternary); |
| 401 | } else { |
| 402 | const flags = c.air.instFlags(inst); |
| 403 | const uses = flags.uses; |
| 404 | |
| 405 | const target = if (uses != .none) |
| 406 | try c.addTemporary() |
| 407 | else |
| 408 | null; |
| 409 | |
| 410 | var yes = try c.codeGenBranch(extra.yes, target); |
| 411 | var no = try c.codeGenBranch(extra.no, target); |
| 412 | assert(!((yes == null) and (no == null))); // no "void else void" branches |
| 413 | |
| 414 | // when `yes` is void, swap the condition so that the `else` is omitted |
| 415 | if (yes == null) { |
| 416 | const temp_yes = yes; |
| 417 | yes = no; |
| 418 | no = temp_yes; |
| 419 | cond = try c.add(.bool_not, .{ |
| 420 | .operand = cond, |
| 421 | }); |
| 422 | } |
| 423 | |
| 424 | const main_expr = try c.add(.stmt_if, .{ |
| 425 | .cond = cond, |
| 426 | .data = try c.extra.append(c.gpa, Tree.StmtIf.Data, .{ |
| 427 | .yes = yes.?.list, |
| 428 | .no = if (no) |branch| |
| 429 | branch.list |
| 430 | else |
| 431 | .{ .start = 0, .len = 0 }, |
| 432 | .flags = .{ |
| 433 | .block_label = @intCast(c.next_label + 1), |
| 434 | .yes_label = yes.?.requires_label, |
| 435 | .no_label = if (no) |branch| branch.requires_label else false, |
| 436 | }, |
| 437 | }), |
| 438 | }); |
| 439 | |
| 440 | try c.addStmt(main_expr); |
| 441 | |
| 442 | if (target) |node| { |
| 443 | try c.markExpression(inst, node); |
| 444 | } |
| 445 | } |
| 446 | }, |
| 447 | |
| 448 | .@"break" => { |
| 449 | const brk: Air.Break = c.air.instData(.@"break", inst); |
| 450 | |
| 451 | // break #0 -> return |
| 452 | if (brk.block == .main_block) { |
| 453 | if (brk.value.isVoid()) { |
| 454 | // If this is a void break at the end of the main block, nothing |
| 455 | // needs to be emitted as that is already what happens at the end |
| 456 | // of a block. |
| 457 | // |
| 458 | // This is also required for the main function, which cannot return |
| 459 | // anything, so this prevents that invalid codegen. |
| 460 | const main_insts = c.air.instData(.block, .main_block).get(&c.air); |
| 461 | if (main_insts[main_insts.len - 1] == inst) { |
| 462 | return; |
| 463 | } |
| 464 | |
| 465 | try c.addStmt(try c.add(.stmt_return, .{ .operand = .none })); |
| 466 | } else { |
| 467 | try c.addStmt(try c.add(.stmt_return, .{ |
| 468 | .operand = (try c.fetchExpression(brk.value)).toOptional(), |
| 469 | })); |
| 470 | } |
| 471 | } else { |
| 472 | const operand = try c.fetchExpression(brk.value); |
| 473 | const block = &c.air_node_map[@intFromEnum(brk.block)].block; |
| 474 | |
| 475 | const block_insts = c.air.instData(.block, brk.block).get(&c.air); |
| 476 | if (block_insts[block_insts.len - 1] != inst) { |
| 477 | block.used = true; |
| 478 | } |
| 479 | |
| 480 | const assignment: Node.Index.Optional = @enumFromInt(c.scratch.items[block.index]); |
| 481 | if (assignment.unwrap()) |target| { |
| 482 | try c.addStmt(try c.add(.assign, .{ |
| 483 | .lhs = target, |
| 484 | .rhs = operand, |
| 485 | })); |
| 486 | } |
| 487 | |
| 488 | try c.addStmt(try c.add(.stmt_break, .{ |
| 489 | .label = c.scratch.items[block.index + 1], |
| 490 | })); |
| 491 | } |
| 492 | }, |
| 493 | |
| 494 | .struct_init => { |
| 495 | const struct_init: Air.StructInit = c.air.instData(.struct_init, inst); |
| 496 | const struct_type = struct_init.ty.toStructType(c.ip).?; |
| 497 | |
| 498 | const property_count: u32 = @intCast(struct_type.field_types.len); |
| 499 | const store_offset = try c.extra.alloc(c.gpa, property_count * 2); |
| 500 | |
| 501 | for ( |
| 502 | struct_type.field_keys.get(c.ip), |
| 503 | struct_init.valuesPtr(&c.air), |
| 504 | store_offset.., |
| 505 | store_offset + property_count.., |
| 506 | ) |key, value, key_i, value_i| { |
| 507 | const key_encoded = Tree.ObjectLiteral.Key.Decoded.encode(.{ .static = key }); |
| 508 | c.extra.list.items[key_i] = @bitCast(key_encoded); |
| 509 | const in = try c.fetchExpression(value); |
| 510 | c.extra.list.items[value_i] = @intFromEnum(in); |
| 511 | } |
| 512 | |
| 513 | const node = try c.add(.object_literal, .{ |
| 514 | .extra_index = store_offset, |
| 515 | .property_count = property_count, |
| 516 | }); |
| 517 | try c.markExpression(inst, node); |
| 518 | }, |
| 519 | |
| 520 | .array_init => { |
| 521 | const array_init: Air.ArrayInit = c.air.instData(.array_init, inst); |
| 522 | |
| 523 | const items = array_init.list.get(&c.air).get(&c.air); |
| 524 | const store_offset = try c.extra.alloc(c.gpa, items.len); |
| 525 | |
| 526 | for ( |
| 527 | items, |
| 528 | store_offset.., |
| 529 | ) |item, item_i| { |
| 530 | const in = try c.fetchExpression(item); |
| 531 | c.extra.list.items[item_i] = @intFromEnum(in); |
| 532 | } |
| 533 | |
| 534 | const node = try c.add(.array, .{ |
| 535 | .start = store_offset, |
| 536 | .len = @intCast(items.len), |
| 537 | }); |
| 538 | try c.markExpression(inst, node); |
| 539 | }, |
| 540 | |
| 541 | .tuple_init => { |
| 542 | const tuple_init: Air.TupleInit = c.air.instData(.tuple_init, inst); |
| 543 | |
| 544 | const field_count = tuple_init.ty.toTupleType(c.ip).?.items.len; |
| 545 | const items = tuple_init.field_values; |
| 546 | const store_offset = try c.extra.alloc(c.gpa, field_count); |
| 547 | |
| 548 | for ( |
| 549 | items..items + field_count, |
| 550 | store_offset.., |
| 551 | ) |in_i, expr_i| { |
| 552 | const in = try c.fetchExpression(@bitCast(c.air.extra.list.items[in_i])); |
| 553 | c.extra.list.items[expr_i] = @intFromEnum(in); |
| 554 | } |
| 555 | |
| 556 | const node = try c.add(.array, .{ |
| 557 | .start = store_offset, |
| 558 | .len = @intCast(field_count), |
| 559 | }); |
| 560 | try c.markExpression(inst, node); |
| 561 | }, |
| 562 | } |
| 563 | } |
| 564 | |
| 565 | fn markExpression(c: *NodeGen, inst: Air.Inst.Index, node: Node.Index) !void { |
| 566 | const t = tracer.trace(@src()); |
| 567 | defer t.end(); |
| 568 | |
| 569 | const flags: Air.Flags = c.air.instFlags(inst); |
| 570 | const i = @intFromEnum(inst); |
| 571 | switch (flags.uses) { |
| 572 | .none => { |
| 573 | try c.addStmt(node); |
| 574 | }, |
| 575 | .single => { |
| 576 | // TODO: this is not aware of side effect fences |
| 577 | c.air_node_map[i] = .{ .node = node }; |
| 578 | }, |
| 579 | .multi => { |
| 580 | const decl = try c.add(.stmt_declare_local, .{ |
| 581 | .identifier_index = c.next_id, |
| 582 | .init = node.toOptional(), |
| 583 | }); |
| 584 | try c.addStmt(decl); |
| 585 | const id = try c.add(.local, .{ .id = c.next_id }); |
| 586 | c.next_id += 1; |
| 587 | c.air_node_map[i] = .{ .node = id }; |
| 588 | }, |
| 589 | } |
| 590 | } |
| 591 | |
| 592 | fn fetchExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index { |
| 593 | const t = tracer.trace(@src()); |
| 594 | defer t.end(); |
| 595 | |
| 596 | return switch (ref.decode()) { |
| 597 | .value => |v| c.valueToExpression(v), |
| 598 | .index => |index| fetchInstExpression(c, index), |
| 599 | }; |
| 600 | } |
| 601 | |
| 602 | fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index { |
| 603 | const t = tracer.trace(@src()); |
| 604 | defer t.end(); |
| 605 | |
| 606 | const data = c.air_node_map[@intFromEnum(index)]; |
| 607 | if (core.runtime_safety) { |
| 608 | const flags: Air.Flags = c.air.instFlags(index); |
| 609 | switch (flags.uses) { |
| 610 | .none => { |
| 611 | // incorrectly marked instruction as unused |
| 612 | std.debug.panic("called fetchInstExpression(#{d}), but it was marked as unused", .{ |
| 613 | @intFromEnum(index), |
| 614 | }); |
| 615 | }, |
| 616 | .single => {}, |
| 617 | .multi => { |
| 618 | // multi-references can only duplicate identifier nodes. |
| 619 | assert(switch (c.nodes.items(.tag)[@intFromEnum(data.node)]) { |
| 620 | .local, .global => true, |
| 621 | else => false, |
| 622 | }); |
| 623 | }, |
| 624 | } |
| 625 | } |
| 626 | return data.node; |
| 627 | } |
| 628 | |
| 629 | fn valueToExpression(c: *NodeGen, v: Value) !Node.Index { |
| 630 | const t = tracer.trace(@src()); |
| 631 | defer t.end(); |
| 632 | |
| 633 | const intern_tag = v.valueTag(c.ip); |
| 634 | switch (intern_tag) { |
| 635 | .removed => unreachable, // UAF |
| 636 | |
| 637 | .function_type, |
| 638 | .simple_type, |
| 639 | .struct_type, |
| 640 | .array_type, |
| 641 | .tuple_type, |
| 642 | => unreachable, // Type values are not allowed here |
| 643 | |
| 644 | .int32, |
| 645 | .float, |
| 646 | .comptime_float, |
| 647 | .string, |
| 648 | .simple_value, |
| 649 | .struct_value, |
| 650 | .array_value, |
| 651 | .tuple_value, |
| 652 | => return try c.add(.plain_value, .{ .value = v }), |
| 653 | |
| 654 | .external_value => { |
| 655 | const external = v.toKey(c.ip).external_value; |
| 656 | const first_key = external.key.at(c.ip, 0); |
| 657 | try c.extern_symbols.put( |
| 658 | c.gpa, |
| 659 | first_key.toString().withLen(first_key.length(c.ip)), |
| 660 | {}, |
| 661 | ); |
| 662 | return try c.add(.plain_value, .{ .value = v }); |
| 663 | }, |
| 664 | |
| 665 | .opaque_value => return c.valueToExpression(v.toKey(c.ip).opaque_value), |
| 666 | |
| 667 | // Hoist functions as top level values |
| 668 | // TODO: Consider hoisting large values too |
| 669 | .func => { |
| 670 | try c.top_level_value_deps.put(c.gpa, v, {}); |
| 671 | return try c.add(.top_level_value, .{ .value = v }); |
| 672 | }, |
| 673 | } |
| 674 | } |
| 675 | |
| 676 | fn makeValueInst(c: *NodeGen, k: InternPool.Key) !Node.Index { |
| 677 | return try c.valueToExpression(c.ip.get(c.gpa, k) catch |err| switch (err) { |
| 678 | error.TooManyValues => @panic("TODO: handle TooManyValues"), |
| 679 | else => |e| return e, |
| 680 | }); |
| 681 | } |
| 682 | |
| 683 | fn addTemporary(c: *NodeGen) !Node.Index { |
| 684 | const t = tracer.trace(@src()); |
| 685 | defer t.end(); |
| 686 | |
| 687 | try c.addStmt(try c.add(.stmt_declare_local, .{ |
| 688 | .identifier_index = c.next_id, |
| 689 | .init = .none, |
| 690 | })); |
| 691 | const id = try c.add(.local, .{ .id = c.next_id }); |
| 692 | c.next_id += 1; |
| 693 | return id; |
| 694 | } |
| 695 | |
| 696 | const Branch = struct { |
| 697 | requires_label: bool, |
| 698 | list: Tree.IndexList, |
| 699 | }; |
| 700 | |
| 701 | fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Branch { |
| 702 | const t = tracer.trace(@src()); |
| 703 | defer t.end(); |
| 704 | |
| 705 | const scratch_top = c.scratch.items.len; |
| 706 | defer c.scratch.shrinkRetainingCapacity(scratch_top); |
| 707 | |
| 708 | c.next_label += 1; |
| 709 | defer c.next_label -= 1; |
| 710 | const this_label = c.next_label; |
| 711 | |
| 712 | var requires_label = false; |
| 713 | var items = switch (inst.decode()) { |
| 714 | .value => |v| items: { |
| 715 | if (v == .void) |
| 716 | return null; |
| 717 | if (temporary) |temp| { |
| 718 | const node = try c.valueToExpression(v); |
| 719 | try c.addStmt(try c.add(.assign, .{ |
| 720 | .lhs = temp, |
| 721 | .rhs = node, |
| 722 | })); |
| 723 | } |
| 724 | break :items c.scratch.items[scratch_top..]; |
| 725 | }, |
| 726 | .index => |i| items: { |
| 727 | assert(c.air.instTag(i) == .block); |
| 728 | c.air_node_map[@intFromEnum(i)] = .{ |
| 729 | .block = .{ .index = @intCast(scratch_top) }, |
| 730 | }; |
| 731 | |
| 732 | try c.scratch.appendSlice(c.arena, &.{ |
| 733 | @intFromEnum(if (temporary) |temp| temp.toOptional() else .none), |
| 734 | this_label, |
| 735 | }); |
| 736 | |
| 737 | const block: Air.IndexList = c.air.instData(.block, i); |
| 738 | for (block.get(&c.air)) |item| { |
| 739 | try c.codeGenInst(item); |
| 740 | } |
| 741 | |
| 742 | requires_label = c.air_node_map[@intFromEnum(i)].block.used; |
| 743 | |
| 744 | break :items c.scratch.items[scratch_top + 2 ..]; |
| 745 | }, |
| 746 | }; |
| 747 | |
| 748 | const last_node = c.scratch.items[c.scratch.items.len - 1]; |
| 749 | if (c.nodes.items(.tag)[last_node] == .stmt_break and |
| 750 | c.nodes.items(.data)[last_node].decode(.stmt_break, .stmt_break).label == this_label) |
| 751 | { |
| 752 | items.len -= 1; |
| 753 | c.nodes.len -= 1; |
| 754 | } |
| 755 | |
| 756 | const top = c.extra.list.items.len; |
| 757 | try c.extra.list.appendSlice(c.gpa, items); |
| 758 | return .{ |
| 759 | .requires_label = requires_label, |
| 760 | .list = .{ |
| 761 | .start = @intCast(top), |
| 762 | .len = @intCast(items.len), |
| 763 | }, |
| 764 | }; |
| 765 | } |
| 766 | |
| 767 | fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { |
| 768 | const t = tracer.trace(@src()); |
| 769 | defer t.end(); |
| 770 | |
| 771 | const scratch_top = c.scratch.items.len; |
| 772 | errdefer c.scratch.shrinkRetainingCapacity(scratch_top); |
| 773 | |
| 774 | c.next_label += 1; |
| 775 | defer c.next_label -= 1; |
| 776 | |
| 777 | const next_label = c.next_label; |
| 778 | |
| 779 | try c.scratch.appendSlice(c.arena, &.{ |
| 780 | @intFromEnum(Node.Index.Optional.none), |
| 781 | next_label, |
| 782 | }); |
| 783 | |
| 784 | c.air_node_map[@intFromEnum(inst)] = .{ |
| 785 | .block = .{ .index = @intCast(scratch_top) }, |
| 786 | }; |
| 787 | const block: Air.IndexList = c.air.instData(.block, inst); |
| 788 | |
| 789 | for (block.get(&c.air)) |item| { |
| 790 | try c.codeGenInst(item); |
| 791 | } |
| 792 | |
| 793 | const items = c.scratch.items[scratch_top + 2 ..]; |
| 794 | const top = c.extra.list.items.len; |
| 795 | try c.extra.list.appendSlice(c.gpa, items); |
| 796 | c.scratch.shrinkRetainingCapacity(scratch_top); |
| 797 | try c.addStmt(try c.add(.stmt_block, .{ |
| 798 | .label = c.next_label, |
| 799 | .content = try c.extra.append(c.gpa, Tree.IndexList, .{ |
| 800 | .start = @intCast(top), |
| 801 | .len = @intCast(items.len), |
| 802 | }), |
| 803 | })); |
| 804 | } |
| 805 | |
| 806 | fn codeGenBlockAsExpression(c: *NodeGen, inst: Air.Inst.Index) !Node.Index { |
| 807 | const t = tracer.trace(@src()); |
| 808 | defer t.end(); |
| 809 | |
| 810 | const scratch_top = c.scratch.items.len; |
| 811 | errdefer c.scratch.shrinkRetainingCapacity(scratch_top); |
| 812 | |
| 813 | try c.scratch.appendSlice(c.arena, &.{ |
| 814 | @intFromEnum(Node.Index.Optional.none), |
| 815 | 0, |
| 816 | }); |
| 817 | |
| 818 | c.air_node_map[@intFromEnum(inst)] = .{ |
| 819 | .block = .{ .index = @intCast(scratch_top) }, |
| 820 | }; |
| 821 | const block: Air.IndexList = c.air.instData(.block, inst); |
| 822 | const instructions = block.get(&c.air); |
| 823 | for (instructions[0 .. instructions.len - 1]) |item| { |
| 824 | try c.codeGenInst(item); |
| 825 | } |
| 826 | |
| 827 | const last_break: Air.Break = c.air.instData(.@"break", instructions[instructions.len - 1]); |
| 828 | const last_break_operand = try c.fetchExpression(last_break.value); |
| 829 | |
| 830 | try c.scratch.append(c.arena, @intFromEnum(last_break_operand)); |
| 831 | |
| 832 | const items = c.scratch.items[scratch_top + 2 ..]; |
| 833 | const top = c.extra.list.items.len; |
| 834 | defer c.scratch.shrinkRetainingCapacity(scratch_top); |
| 835 | assert(items.len > 0); |
| 836 | if (items.len == 1) { |
| 837 | return @enumFromInt(items[0]); |
| 838 | } |
| 839 | try c.extra.list.appendSlice(c.gpa, items); |
| 840 | return try c.add(.comma_chain, .{ |
| 841 | .start = @intCast(top), |
| 842 | .len = @intCast(items.len), |
| 843 | }); |
| 844 | } |
| 845 | |
| 846 | // Ideally, we convert everything to an expression. This allows for much |
| 847 | // more compact code. When that isnt possible (`return`, `while`, etc), |
| 848 | // we detect that here and ensure to lower `if` to actual if statements, |
| 849 | // and so on. |
| 850 | fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 851 | const t = tracer.trace(@src()); |
| 852 | defer t.end(); |
| 853 | |
| 854 | return switch (inst.decode()) { |
| 855 | .value => true, |
| 856 | .index => |index| switch (c.air.instTag(index)) { |
| 857 | .call, |
| 858 | .js_new, |
| 859 | .arg, |
| 860 | .field_opaque, |
| 861 | .field_struct, |
| 862 | .field_tuple, |
| 863 | .field_array, |
| 864 | .add, |
| 865 | .sub, |
| 866 | .mul, |
| 867 | .div, |
| 868 | .eq, |
| 869 | .lt, |
| 870 | .gt, |
| 871 | .ge, |
| 872 | .le, |
| 873 | .negate, |
| 874 | .bool_not, |
| 875 | .store, |
| 876 | .struct_init, |
| 877 | .array_init, |
| 878 | .array_len, |
| 879 | .tuple_init, |
| 880 | => true, |
| 881 | |
| 882 | // JavaScript variable declarations require statements. |
| 883 | // Later, this could be made true if we can hoist temporaries up. |
| 884 | .memory => false, |
| 885 | |
| 886 | // these must be lowered to js statements |
| 887 | .@"break", .panic => false, |
| 888 | |
| 889 | // A block can become a comma expression if all expressions |
| 890 | // are lowerable. The break at the end just becomes the result. |
| 891 | .block => { |
| 892 | const block: Air.IndexList = c.air.instData(.block, index); |
| 893 | const items = block.get(&c.air); |
| 894 | for (items[0 .. items.len - 1]) |item| { |
| 895 | if (c.air.instFlags(item).uses == .multi) |
| 896 | return false; |
| 897 | if (!c.canLowerToExpression(item.toRef())) |
| 898 | return false; |
| 899 | } |
| 900 | const last_item = items[items.len - 1]; |
| 901 | if (c.air.instTag(last_item) != .@"break") { |
| 902 | return c.canLowerToExpression(last_item.toRef()); |
| 903 | } |
| 904 | const brk = c.air.instData(.@"break", last_item); |
| 905 | // The last break will only be lowerable if it |
| 906 | // breaks out of this specific block |
| 907 | return brk.block == index; |
| 908 | }, |
| 909 | |
| 910 | .@"if" => { |
| 911 | const call = c.air.instData(.@"if", index); |
| 912 | const data = call.data.get(&c.air); |
| 913 | |
| 914 | return c.canLowerToExpression(call.cond) and |
| 915 | c.canLowerToExpression(data.yes) and |
| 916 | c.canLowerToExpression(data.no); |
| 917 | }, |
| 918 | }, |
| 919 | }; |
| 920 | } |
| 921 | |
| 922 | fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index { |
| 923 | const t = tracer.trace(@src()); |
| 924 | defer t.end(); |
| 925 | |
| 926 | assert(c.canLowerToExpression(ref)); |
| 927 | return switch (ref.decode()) { |
| 928 | .value => |v| try c.valueToExpression(v), |
| 929 | .index => |index| { |
| 930 | if (c.air.instTag(index) == .block) { |
| 931 | return try c.codeGenBlockAsExpression(index); |
| 932 | } |
| 933 | try c.codeGenInst(index); |
| 934 | return c.fetchExpression(ref); |
| 935 | }, |
| 936 | }; |
| 937 | } |
| 938 | |
| 939 | fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index { |
| 940 | const t = tracer.trace(@src()); |
| 941 | defer t.end(); |
| 942 | |
| 943 | try c.nodes.append(c.gpa, .{ |
| 944 | .tag = tag, |
| 945 | .data = .encode(tag, data), |
| 946 | }); |
| 947 | return @enumFromInt(c.nodes.len - 1); |
| 948 | } |
| 949 | |
| 950 | fn addStmt(c: *NodeGen, stmt: Node.Index) !void { |
| 951 | const t = tracer.trace(@src()); |
| 952 | defer t.end(); |
| 953 | |
| 954 | try c.scratch.append(c.arena, @intFromEnum(stmt)); |
| 955 | } |
| 956 | |
| 957 | const NodeGen = @This(); |
| 958 | |
| 959 | const std = @import("std"); |
| 960 | const assert = std.debug.assert; |
| 961 | const Allocator = std.mem.Allocator; |
| 962 | |
| 963 | const core = @import("../mod.zig"); |
| 964 | const InternPool = core.InternPool; |
| 965 | const Value = InternPool.Value; |
| 966 | const NullTerminatedString = InternPool.NullTerminatedString; |
| 967 | const Comp = core.Comp; |
| 968 | const Decl = Comp.Decl; |
| 969 | const Tree = core.Js.Tree; |
| 970 | const EncodedData = Tree.EncodedData; |
| 971 | const Node = Tree.Node; |
| 972 | const LocalIndex = Tree.LocalIndex; |
| 973 | const Air = core.Air; |
| 974 | const dod = core.array; |
| 975 | |
| 976 | const tracer = @import("tracer"); |