| author | |
| committer | |
| log | fd9bc5492894391c36ce4ee485e103ceb0255f76 |
| tree | afb74799a604f6c032ed06791f284a138b15ce67 |
| parent | 230648717c1f197ece3aec476cda99ad3fa158a6 |
array out of bounds should raise an unrecoverable error. this will also
happen for addition out of bounds, runtime assertions, and other things.
safety checks are generated Air instructions using existing constructs
like `if`, with a new instruction tag: `panic` which is intended to
raise an unrecoverable error. using air lets these checks lower to any
backend without additional work, as well as take advantage of existing
and future optimizations.
this also adds `bool_and` and `bool_or` in the Transform pass, but these
generate `if` instructions to re-use logic for short circuiting.
the js backend can't actually make an unrecoverable error. since try
catch blocks are banned, this is fine. in a node.js target, NodeGen
could make this use `process.exit`. for wasm and native, this can lower
to a log combined with a trap instruction.
18 files changed, 459 insertions(+), 116 deletions(-)
CHANGELOG.md+3| ... | ... | @@ -2,11 +2,14 @@ |
| 2 | 2 | |
| 3 | 3 | - chore: rename the language to "ix" |
| 4 | 4 | - feat: language manual gets a pleasing redesign |
| 5 | - feat: add boolean `and` and `or` operator keywords | |
| 5 | 6 | - feat: allow any expression as a function body |
| 6 | 7 | - feat: add `assert` keyword |
| 8 | - feat: add safety checks for array out of bounds | |
| 7 | 9 | - fix: coerce `break` operands and implicit breaks to result type |
| 8 | 10 | - fix: coerce results when `if` expression branches merge |
| 9 | 11 | - fix: ensure if branch results to a runtime value when the condition is not compile-time known |
| 12 | - fix: inferred array type uses item type for items instead of the container type. | |
| 10 | 13 | - fix(js): stop printing redundant parenthesis on ternary expressions |
| 11 | 14 | |
| 12 | 15 | ## 0.1.6 |
build.zig+2-1| ... | ... | @@ -594,7 +594,8 @@ fn crossTarget(cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag) Build.R |
| 594 | 594 | }; |
| 595 | 595 | return .{ |
| 596 | 596 | .query = query, |
| 597 | .result = std.zig.system.resolveTargetQuery(query) catch unreachable, | |
| 597 | .result = std.zig.system.resolveTargetQuery(query) catch | |
| 598 | @panic("unable to resolve target query"), | |
| 598 | 599 | }; |
| 599 | 600 | } |
| 600 | 601 |
build.zig.zon+1-1| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | .name = "", |
| 3 | 3 | .version = "0.0.0", |
| 4 | 4 | |
| 5 | .minimum_zig_version = "0.14.0-dev.1511+54b668f8a", | |
| 5 | .minimum_zig_version = "0.14.0-dev.2034+56996a280", | |
| 6 | 6 | |
| 7 | 7 | .dependencies = .{ |
| 8 | 8 | // Used to determine unicode properties to tokenize identifiers |
src/core/Air.Dump.zig+3| ... | ... | @@ -87,6 +87,8 @@ pub const State = struct { |
| 87 | 87 | .lt, |
| 88 | 88 | .gt, |
| 89 | 89 | .eq, |
| 90 | .le, | |
| 91 | .ge, | |
| 90 | 92 | .field_array, |
| 91 | 93 | .field_opaque, |
| 92 | 94 | => |comptime_tag| { |
| ... | ... | @@ -100,6 +102,7 @@ pub const State = struct { |
| 100 | 102 | inline .negate, |
| 101 | 103 | .bool_not, |
| 102 | 104 | .array_len, |
| 105 | .panic, | |
| 103 | 106 | => |comptime_tag| { |
| 104 | 107 | const bin: Air.Un = air.instData(comptime_tag, inst); |
| 105 | 108 | try w.writeAll("("); |
src/core/Air.zig+10-6| ... | ... | @@ -34,20 +34,19 @@ pub const Inst = struct { |
| 34 | 34 | |
| 35 | 35 | array_len, |
| 36 | 36 | |
| 37 | // bin | |
| 38 | 37 | add, |
| 39 | 38 | sub, |
| 40 | 39 | mul, |
| 41 | 40 | div, |
| 42 | ||
| 43 | 41 | eq, |
| 44 | ||
| 45 | 42 | lt, |
| 43 | le, | |
| 46 | 44 | gt, |
| 45 | ge, | |
| 47 | 46 | |
| 48 | // un | |
| 49 | 47 | negate, |
| 50 | 48 | bool_not, |
| 49 | panic, | |
| 51 | 50 | |
| 52 | 51 | pub fn Data(comptime tag: Tag) type { |
| 53 | 52 | return switch (tag) { |
| ... | ... | @@ -71,10 +70,13 @@ pub const Inst = struct { |
| 71 | 70 | .lt, |
| 72 | 71 | .gt, |
| 73 | 72 | .eq, |
| 73 | .le, | |
| 74 | .ge, | |
| 74 | 75 | => Bin, |
| 75 | 76 | .negate, |
| 76 | 77 | .bool_not, |
| 77 | 78 | .array_len, |
| 79 | .panic, | |
| 78 | 80 | => Un, |
| 79 | 81 | }; |
| 80 | 82 | } |
| ... | ... | @@ -85,6 +87,8 @@ pub const Inst = struct { |
| 85 | 87 | kind: Kind, |
| 86 | 88 | data: u31, |
| 87 | 89 | |
| 90 | pub const @"void" = Value.void.toAir(); | |
| 91 | ||
| 88 | 92 | pub const Kind = enum(u1) { value, index }; |
| 89 | 93 | |
| 90 | 94 | pub const Decoded = union(enum) { |
| ... | ... | @@ -345,8 +349,8 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In |
| 345 | 349 | .js_new => .@"opaque", |
| 346 | 350 | |
| 347 | 351 | // These operations always produce known types |
| 348 | .lt, .gt, .eq, .bool_not => .bool, | |
| 349 | .@"break" => .never, | |
| 352 | .lt, .gt, .le, .ge, .eq, .bool_not => .bool, | |
| 353 | .@"break", .panic => .never, | |
| 350 | 354 | .field_opaque => .@"opaque", |
| 351 | 355 | .array_len => .int32, |
| 352 | 356 |
src/core/Ast.zig+6-6| ... | ... | @@ -144,9 +144,9 @@ pub const Node = struct { |
| 144 | 144 | /// `lhs ** rhs`. main_token is the operator. |
| 145 | 145 | pow, |
| 146 | 146 | /// `lhs && rhs`. main_token is the operator. |
| 147 | @"and", | |
| 147 | bool_and, | |
| 148 | 148 | /// `lhs || rhs`. main_token is the operator. |
| 149 | @"or", | |
| 149 | bool_or, | |
| 150 | 150 | /// `lhs & rhs`. main_token is the operator. |
| 151 | 151 | bit_and, |
| 152 | 152 | /// `lhs | rhs`. main_token is the operator. |
| ... | ... | @@ -247,8 +247,8 @@ pub const Node = struct { |
| 247 | 247 | .div, |
| 248 | 248 | .rem, |
| 249 | 249 | .pow, |
| 250 | .@"and", | |
| 251 | .@"or", | |
| 250 | .bool_and, | |
| 251 | .bool_or, | |
| 252 | 252 | .bit_and, |
| 253 | 253 | .bit_or, |
| 254 | 254 | .bit_xor, |
| ... | ... | @@ -280,11 +280,11 @@ pub const Node = struct { |
| 280 | 280 | |
| 281 | 281 | pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{ |
| 282 | 282 | // sort-lines:start |
| 283 | .@"and" = .bin, | |
| 283 | .bool_and = .bin, | |
| 284 | 284 | .@"break" = .@"break", |
| 285 | 285 | .@"comptime" = .un, |
| 286 | 286 | .@"if" = .@"if", |
| 287 | .@"or" = .bin, | |
| 287 | .bool_or = .bin, | |
| 288 | 288 | .@"return" = .@"return", |
| 289 | 289 | .add = .bin, |
| 290 | 290 | .add_assign = .bin, |
src/core/Hir.Dump.zig+1-1| ... | ... | @@ -307,7 +307,7 @@ const State = struct { |
| 307 | 307 | |
| 308 | 308 | if (data.type_hint.unwrap()) |type_hint| { |
| 309 | 309 | try writer.writeAll("("); |
| 310 | try s.dumpRef(writer, type_hint); | |
| 310 | try s.dumpRefMarker(writer, type_hint); | |
| 311 | 311 | try writer.writeAll(", "); |
| 312 | 312 | } |
| 313 | 313 |
src/core/Hir.zig+5-2| ... | ... | @@ -60,9 +60,12 @@ pub const Tag = enum(u8) { |
| 60 | 60 | /// Asserts the operand is `void`. Used for undiscarded expressions. |
| 61 | 61 | ensure_void, |
| 62 | 62 | |
| 63 | /// Branch takes a boolean condition and takes either a yes or no path. | |
| 63 | // TODO: Rename to `.branch` | |
| 64 | /// Given a boolean condition, pick one of two branches. This is used to | |
| 65 | /// implement `if` statements as well as boolean `and` / `or` operators. | |
| 64 | 66 | @"if", |
| 65 | /// Given a block instruction | |
| 67 | /// Given a block instruction, evaluate to a Value without generating | |
| 68 | /// any runtime instructions. | |
| 66 | 69 | @"comptime", |
| 67 | 70 | |
| 68 | 71 | /// A function expression, with or without captures |
src/core/Parse.zig+15-14| ... | ... | @@ -111,8 +111,8 @@ const Operator = enum(u8) { |
| 111 | 111 | div = @intFromEnum(Token.Tag.slash), |
| 112 | 112 | rem = @intFromEnum(Token.Tag.percent), |
| 113 | 113 | pow = @intFromEnum(Token.Tag.star_star), |
| 114 | @"and" = @intFromEnum(Token.Tag.and_and), | |
| 115 | @"or" = @intFromEnum(Token.Tag.pipe_pipe), | |
| 114 | bool_and = @intFromEnum(Token.Tag.kw_and), | |
| 115 | bool_or = @intFromEnum(Token.Tag.kw_or), | |
| 116 | 116 | bit_and = @intFromEnum(Token.Tag.@"and"), |
| 117 | 117 | bit_or = @intFromEnum(Token.Tag.pipe), |
| 118 | 118 | bit_xor = @intFromEnum(Token.Tag.caret), |
| ... | ... | @@ -209,8 +209,8 @@ const Operator = enum(u8) { |
| 209 | 209 | .shl, .shr => .bitwise_shift, |
| 210 | 210 | .lt, .le, .gt, .ge => .relational, |
| 211 | 211 | .eq, .ne => .equality, |
| 212 | .@"and" => .logical_and, | |
| 213 | .@"or" => .logical_or, | |
| 212 | .bool_and => .logical_and, | |
| 213 | .bool_or => .logical_or, | |
| 214 | 214 | .bit_and => .bit_and, |
| 215 | 215 | .bit_xor => .bit_xor, |
| 216 | 216 | .bit_or => .bit_or, |
| ... | ... | @@ -1493,14 +1493,15 @@ const Comp = core.Comp; |
| 1493 | 1493 | |
| 1494 | 1494 | test "fuzz test parser for crashes" { |
| 1495 | 1495 | try core.unicode_props.init(); |
| 1496 | ||
| 1497 | const input_bytes = std.testing.fuzzInput(.{}); | |
| 1498 | ||
| 1499 | const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes); | |
| 1500 | defer std.testing.allocator.free(sentinel); | |
| 1501 | ||
| 1502 | var err: AstError.WithTrace = undefined; | |
| 1503 | var ast = parse(std.testing.allocator, sentinel, &err) catch | |
| 1504 | return; // ignore parser errors, only fail on crashes | |
| 1505 | ast.deinit(std.testing.allocator); | |
| 1496 | try std.testing.fuzz(struct { | |
| 1497 | fn run(input_bytes: []const u8) !void { | |
| 1498 | const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes); | |
| 1499 | defer std.testing.allocator.free(sentinel); | |
| 1500 | ||
| 1501 | var err: AstError.WithTrace = undefined; | |
| 1502 | var ast = parse(std.testing.allocator, sentinel, &err) catch | |
| 1503 | return; // ignore parser errors, only fail on crashes | |
| 1504 | ast.deinit(std.testing.allocator); | |
| 1505 | } | |
| 1506 | }.run, .{}); | |
| 1506 | 1507 | } |
src/core/Sema.zig+153-38| ... | ... | @@ -13,6 +13,8 @@ arena_state: std.heap.ArenaAllocator, |
| 13 | 13 | /// When this is .decl, we are producing a `Value` |
| 14 | 14 | owner: AnalysisUnit, |
| 15 | 15 | |
| 16 | generate_safety_checks: bool = true, | |
| 17 | ||
| 16 | 18 | /// The namespace index that is used for declaration lookups |
| 17 | 19 | ns_index: Namespace.Index, |
| 18 | 20 | /// Non-owning copy |
| ... | ... | @@ -358,8 +360,9 @@ fn markInst(s: *Sema, inst: Air.Inst.Index) void { |
| 358 | 360 | flags.uses = flags.uses.increment(); |
| 359 | 361 | } |
| 360 | 362 | |
| 361 | // TODO: name when awaake pls | |
| 362 | const DoMark = enum { | |
| 363 | const LivenessMark = enum { | |
| 364 | /// The value retrieved will not be used as the operand for another | |
| 365 | /// instruction. | |
| 363 | 366 | no_mark, |
| 364 | 367 | /// A Ref must be marked if it's value is used as the operand for another |
| 365 | 368 | /// Air instruction. This informs the Js backend if an expression must |
| ... | ... | @@ -367,7 +370,7 @@ const DoMark = enum { |
| 367 | 370 | marked, |
| 368 | 371 | }; |
| 369 | 372 | |
| 370 | fn getRef(s: *Sema, teg_ref: Hir.Inst.Ref, mark: DoMark) Air.Inst.Ref { | |
| 373 | fn getRef(s: *Sema, teg_ref: Hir.Inst.Ref, mark: LivenessMark) Air.Inst.Ref { | |
| 371 | 374 | switch (teg_ref.decode()) { |
| 372 | 375 | .value => |value| return value.toAir(), |
| 373 | 376 | .index => |index| { |
| ... | ... | @@ -400,7 +403,7 @@ fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type) !Va |
| 400 | 403 | return try s.coerceValue(src, any, ty); |
| 401 | 404 | } |
| 402 | 405 | |
| 403 | fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type, mark: DoMark) !Air.Inst.Ref { | |
| 406 | fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type, mark: LivenessMark) !Air.Inst.Ref { | |
| 404 | 407 | const any = s.getRef(teg_ref, mark); |
| 405 | 408 | return try s.coerceRef(src, any, ty); |
| 406 | 409 | } |
| ... | ... | @@ -619,6 +622,8 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 619 | 622 | }); |
| 620 | 623 | } |
| 621 | 624 | |
| 625 | try s.ensureAtRuntime(s.lazyNode(.operand)); | |
| 626 | ||
| 622 | 627 | return s.addRef(.negate, .{ .operand = operand }); |
| 623 | 628 | }, |
| 624 | 629 | .bool_not => { |
| ... | ... | @@ -799,10 +804,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 799 | 804 | const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, node.list.len); |
| 800 | 805 | defer scratch.deinit(); |
| 801 | 806 | |
| 802 | var item_type: ?Type = if (node.type_hint.unwrap()) |ty| | |
| 803 | try s.getType(s.lazyNode(.operand), ty) | |
| 804 | else | |
| 805 | null; | |
| 807 | var item_type: ?Type = if (node.type_hint.unwrap()) |ty| inferred_type: { | |
| 808 | const parent_type = try s.getType(s.lazyNode(.operand), ty); | |
| 809 | const array_data: InternPool.Key.ArrayType = parent_type.toArrayType(s.ip) orelse | |
| 810 | // Allow the coercion to fail later. | |
| 811 | break :inferred_type null; | |
| 812 | break :inferred_type array_data.item_type; | |
| 813 | } else null; | |
| 806 | 814 | |
| 807 | 815 | var is_constant = true; |
| 808 | 816 | |
| ... | ... | @@ -1103,13 +1111,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1103 | 1111 | |
| 1104 | 1112 | switch (data.conditional) { |
| 1105 | 1113 | .comptime_condition => { |
| 1106 | assert(operand.kind == .value); // must be comptime | |
| 1107 | 1114 | s.inst_map.add(s.frame.currentIndex(), operand); |
| 1108 | 1115 | s.frame.advance(); |
| 1109 | 1116 | return .@"continue"; |
| 1110 | 1117 | }, |
| 1111 | 1118 | .runtime_yes => { |
| 1112 | try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark); | |
| 1119 | try s.emitRuntimeBranchForIf(if_data.yes.decode().index, operand, mark); | |
| 1113 | 1120 | mark.deinit(); |
| 1114 | 1121 | |
| 1115 | 1122 | switch (if_data.no.decode()) { |
| ... | ... | @@ -1127,7 +1134,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1127 | 1134 | } |
| 1128 | 1135 | }, |
| 1129 | 1136 | .runtime_no => { |
| 1130 | try s.emitRuntimeBlock(if_data.no.decode().index, operand, mark); | |
| 1137 | try s.emitRuntimeBranchForIf(if_data.no.decode().index, operand, mark); | |
| 1131 | 1138 | mark.deinit(); |
| 1132 | 1139 | }, |
| 1133 | 1140 | } |
| ... | ... | @@ -1140,6 +1147,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1140 | 1147 | if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir(); |
| 1141 | 1148 | if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir(); |
| 1142 | 1149 | |
| 1150 | if (yes.value() == no.value()) { | |
| 1151 | s.inst_map.add(s.frame.currentIndex(), yes); | |
| 1152 | s.frame.advance(); | |
| 1153 | return .@"continue"; | |
| 1154 | } | |
| 1155 | ||
| 1143 | 1156 | const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) }); |
| 1144 | 1157 | |
| 1145 | 1158 | const yes_coerced = try s.coerceBranch(s.lazyNode(.lhs), yes, union_type); |
| ... | ... | @@ -1166,11 +1179,15 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1166 | 1179 | |
| 1167 | 1180 | const return_type = switch (data.func) { |
| 1168 | 1181 | .argument => |arg_num| ret: { |
| 1182 | s.inst_map.add( | |
| 1183 | func_data.params.slice(&s.hir)[arg_num].unwrap().?.decode().index, | |
| 1184 | operand, | |
| 1185 | ); | |
| 1186 | ||
| 1169 | 1187 | for ( |
| 1170 | 1188 | func_data.params.slice(&s.hir)[arg_num + 1 ..], |
| 1171 | reserve.items(.params)[arg_num + 1 ..], | |
| 1172 | 1189 | arg_num + 1.., |
| 1173 | ) |param, *dst, i| { | |
| 1190 | ) |param, i| { | |
| 1174 | 1191 | const known = param.unwrap() orelse @panic("TODO: inferred param sets"); |
| 1175 | 1192 | switch (known.decode()) { |
| 1176 | 1193 | .value => |val| { |
| ... | ... | @@ -1181,7 +1198,6 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1181 | 1198 | .type = ty, |
| 1182 | 1199 | }); |
| 1183 | 1200 | } |
| 1184 | dst.* = ty; | |
| 1185 | 1201 | }, |
| 1186 | 1202 | .index => |block| { |
| 1187 | 1203 | const mode = s.frame.mode; |
| ... | ... | @@ -1225,6 +1241,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1225 | 1241 | }, |
| 1226 | 1242 | }; |
| 1227 | 1243 | |
| 1244 | for (func_data.params.slice(&s.hir), reserve.items(.params)) |param, *dst| { | |
| 1245 | dst.* = (switch (param.unwrap().?.decode()) { | |
| 1246 | .value => |value| value, | |
| 1247 | .index => |index| s.inst_map.get(index).decode().value, | |
| 1248 | }).toTypeUnsafe(s.ip); | |
| 1249 | } | |
| 1250 | ||
| 1228 | 1251 | try s.postVisitFunctionPrototype(reserve, return_type); |
| 1229 | 1252 | return .@"continue"; |
| 1230 | 1253 | }, |
| ... | ... | @@ -1319,6 +1342,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1319 | 1342 | const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type); |
| 1320 | 1343 | const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type); |
| 1321 | 1344 | |
| 1345 | if (yes_coerced == no_coerced) { | |
| 1346 | s.inst_map.add(s.frame.currentIndex(), yes_coerced.toAir()); | |
| 1347 | s.frame.advance(); | |
| 1348 | return; | |
| 1349 | } | |
| 1350 | ||
| 1322 | 1351 | s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{ |
| 1323 | 1352 | .cond = condition, |
| 1324 | 1353 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ |
| ... | ... | @@ -1573,39 +1602,54 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag) |
| 1573 | 1602 | } |
| 1574 | 1603 | } |
| 1575 | 1604 | |
| 1576 | fn emitRuntimeBlock( | |
| 1605 | fn emitRuntimeBranchForIf( | |
| 1577 | 1606 | s: *Sema, |
| 1578 | 1607 | from: Hir.Inst.Index, |
| 1579 | break_operand: Air.Inst.Ref, | |
| 1608 | result_value: Air.Inst.Ref, | |
| 1580 | 1609 | mark: core.U32Stack.Mark, |
| 1581 | 1610 | ) !void { |
| 1582 | 1611 | s.inst_map.add( |
| 1583 | 1612 | from, |
| 1584 | if (mark.len() == 0) brk: { | |
| 1585 | assert(break_operand.kind == .value); | |
| 1586 | break :brk break_operand; | |
| 1587 | } else brk: { | |
| 1588 | assert(s.frame.mode == .run_time); | |
| 1589 | const func = &s.func.?; | |
| 1590 | const idx = func.extra.list.items.len; | |
| 1591 | const block_instructions = mark.slice(u32); | |
| 1592 | try func.extra.list.ensureUnusedCapacity(s.gpa, block_instructions.len); | |
| 1593 | const block = try s.addIndexUntracked(.block, .{ | |
| 1594 | .start = @intCast(idx), | |
| 1595 | .len = @intCast(block_instructions.len + 1), | |
| 1596 | }); | |
| 1597 | const brk_instruction = try s.addIndexUntracked(.@"break", .{ | |
| 1598 | .block = block, | |
| 1599 | .value = break_operand, | |
| 1600 | }); | |
| 1601 | func.extra.list.appendSliceAssumeCapacity(block_instructions); | |
| 1602 | func.extra.list.appendAssumeCapacity(@intFromEnum(brk_instruction)); | |
| 1603 | break :brk block.toRef(); | |
| 1604 | }, | |
| 1613 | if (mark.len() == 0 and result_value.kind == .value) brk: { | |
| 1614 | break :brk result_value; | |
| 1615 | } else (try s.createRuntimeBlock(mark, result_value)).toRef(), | |
| 1605 | 1616 | ); |
| 1606 | 1617 | mark.deinit(); |
| 1607 | 1618 | } |
| 1608 | 1619 | |
| 1620 | fn createRuntimeBlock(s: *Sema, mark: core.U32Stack.Mark, result: ?Air.Inst.Ref) !Air.Inst.Index { | |
| 1621 | assert(mark.len() > 0); | |
| 1622 | assert(s.frame.mode == .run_time); | |
| 1623 | ||
| 1624 | const func = &s.func.?; | |
| 1625 | const idx = func.extra.list.items.len; | |
| 1626 | ||
| 1627 | if (core.runtime_safety) { | |
| 1628 | const ty = s.tempAir().typeOfIndex(mark.slice(Air.Inst.Index)[0], s.ip); | |
| 1629 | if (result != null) { | |
| 1630 | assert(ty != .never); // control flow already ended | |
| 1631 | } else { | |
| 1632 | assert(ty == .never); // does not stop control flow | |
| 1633 | } | |
| 1634 | } | |
| 1635 | ||
| 1636 | const block_instructions = mark.slice(u32); | |
| 1637 | try func.extra.list.ensureUnusedCapacity(s.gpa, block_instructions.len); | |
| 1638 | const block = try s.addIndexUntracked(.block, .{ | |
| 1639 | .start = @intCast(idx), | |
| 1640 | .len = @intCast(block_instructions.len + @intFromBool(result != null)), | |
| 1641 | }); | |
| 1642 | func.extra.list.appendSliceAssumeCapacity(block_instructions); | |
| 1643 | if (result) |value| { | |
| 1644 | const brk_instruction = try s.addIndexUntracked(.@"break", .{ | |
| 1645 | .block = block, | |
| 1646 | .value = value, | |
| 1647 | }); | |
| 1648 | func.extra.list.appendAssumeCapacity(@intFromEnum(brk_instruction)); | |
| 1649 | } | |
| 1650 | return block; | |
| 1651 | } | |
| 1652 | ||
| 1609 | 1653 | const BinaryOp = enum { |
| 1610 | 1654 | add, |
| 1611 | 1655 | sub, |
| ... | ... | @@ -1889,7 +1933,8 @@ fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst. |
| 1889 | 1933 | return switch (ref.decode()) { |
| 1890 | 1934 | .index => |block| { |
| 1891 | 1935 | if (s.typeOfIndex(block) != to) return ref; |
| 1892 | @panic("TODO"); | |
| 1936 | // @panic("TODO"); | |
| 1937 | return ref; | |
| 1893 | 1938 | }, |
| 1894 | 1939 | .value => |value| (try s.coerceValue(src, value, to)).toAir(), |
| 1895 | 1940 | }; |
| ... | ... | @@ -2225,6 +2270,38 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In |
| 2225 | 2270 | |
| 2226 | 2271 | try s.ensureAtRuntime(s.lazyNode(.main_node)); |
| 2227 | 2272 | |
| 2273 | if (s.generate_safety_checks) { | |
| 2274 | const is_above_bounds = try s.addRefMark(.ge, .{ | |
| 2275 | .lhs = index, | |
| 2276 | .rhs = try s.addRefMark(.array_len, .{ | |
| 2277 | .operand = lhs, | |
| 2278 | }), | |
| 2279 | }); | |
| 2280 | const is_oob = switch (index.decode()) { | |
| 2281 | .value => |value| is_oob: { | |
| 2282 | const int = value.toInt32(s.ip) orelse unreachable; | |
| 2283 | if (int < 0) @panic("TODO: always out of bounds"); | |
| 2284 | break :is_oob is_above_bounds; | |
| 2285 | }, | |
| 2286 | .index => is_oob: { | |
| 2287 | const is_below_zero = brk: { | |
| 2288 | const mark = s.scratch.mark(); | |
| 2289 | defer mark.deinit(); | |
| 2290 | ||
| 2291 | const is_below_zero = try s.addRefMark(.lt, .{ | |
| 2292 | .lhs = index, | |
| 2293 | .rhs = try s.makeValueAir(.{ .int32 = 0 }), | |
| 2294 | }); | |
| 2295 | ||
| 2296 | break :brk try s.createRuntimeBlock(mark, is_below_zero); | |
| 2297 | }; | |
| 2298 | break :is_oob try s.emitOrFromRefAndBlock(is_above_bounds, is_below_zero); | |
| 2299 | }, | |
| 2300 | }; | |
| 2301 | ||
| 2302 | try s.emitSafetyCheck(is_oob, "array out of bounds"); | |
| 2303 | } | |
| 2304 | ||
| 2228 | 2305 | return s.addRef(.field_array, .{ |
| 2229 | 2306 | .lhs = lhs, |
| 2230 | 2307 | .rhs = index, |
| ... | ... | @@ -2233,6 +2310,17 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In |
| 2233 | 2310 | } |
| 2234 | 2311 | } |
| 2235 | 2312 | |
| 2313 | fn emitOrFromRefAndBlock(s: *Sema, lhs: Air.Inst.Ref, short_circuiting_rhs: Air.Inst.Index) !Air.Inst.Ref { | |
| 2314 | return try s.addRefMark(.@"if", .{ | |
| 2315 | .cond = lhs, | |
| 2316 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ | |
| 2317 | .yes = Value.bool_true.toAir(), | |
| 2318 | .no = short_circuiting_rhs.toRef(), | |
| 2319 | .union_ty = .bool, | |
| 2320 | }), | |
| 2321 | }); | |
| 2322 | } | |
| 2323 | ||
| 2236 | 2324 | fn fieldAccessTuple( |
| 2237 | 2325 | s: *Sema, |
| 2238 | 2326 | lhs: Air.Inst.Ref, |
| ... | ... | @@ -2297,6 +2385,27 @@ fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ? |
| 2297 | 2385 | try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } }); |
| 2298 | 2386 | } |
| 2299 | 2387 | |
| 2388 | fn emitSafetyCheck(s: *Sema, should_panic: Air.Inst.Ref, panic_message: []const u8) !void { | |
| 2389 | const yes = brk: { | |
| 2390 | const mark = s.scratch.mark(); | |
| 2391 | defer mark.deinit(); | |
| 2392 | ||
| 2393 | _ = try s.addRefMark(.panic, .{ | |
| 2394 | .operand = try s.makeValueAir(.{ .string = panic_message }), | |
| 2395 | }); | |
| 2396 | ||
| 2397 | break :brk try s.createRuntimeBlock(mark, null); | |
| 2398 | }; | |
| 2399 | _ = try s.addRefMark(.@"if", .{ | |
| 2400 | .cond = should_panic, | |
| 2401 | .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{ | |
| 2402 | .yes = yes.toRef(), | |
| 2403 | .no = .void, | |
| 2404 | .union_ty = .void, | |
| 2405 | }), | |
| 2406 | }); | |
| 2407 | } | |
| 2408 | ||
| 2300 | 2409 | // |
| 2301 | 2410 | |
| 2302 | 2411 | fn tempAir(s: *const Sema) Air { |
| ... | ... | @@ -2342,6 +2451,12 @@ fn addRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref |
| 2342 | 2451 | return (try s.addIndex(tag, data)).toRef(); |
| 2343 | 2452 | } |
| 2344 | 2453 | |
| 2454 | fn addRefMark(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref { | |
| 2455 | const inst = try s.addIndex(tag, data); | |
| 2456 | s.markInst(inst); | |
| 2457 | return inst.toRef(); | |
| 2458 | } | |
| 2459 | ||
| 2345 | 2460 | fn typeOfIndex(s: *Sema, i: Air.Inst.Index) Type { |
| 2346 | 2461 | return s.tempAir().typeOfIndex(i, s.ip); |
| 2347 | 2462 | } |
src/core/Tokenizer.zig+2| ... | ... | @@ -55,6 +55,7 @@ pub const Token = struct { |
| 55 | 55 | module_doc_comment, |
| 56 | 56 | |
| 57 | 57 | // sort-lines: start |
| 58 | kw_and, | |
| 58 | 59 | kw_assert, |
| 59 | 60 | kw_break, |
| 60 | 61 | kw_comptime, |
| ... | ... | @@ -70,6 +71,7 @@ pub const Token = struct { |
| 70 | 71 | kw_let, |
| 71 | 72 | kw_match, |
| 72 | 73 | kw_module, |
| 74 | kw_or, | |
| 73 | 75 | kw_pub, |
| 74 | 76 | kw_pure, |
| 75 | 77 | kw_return, |
src/core/Transform.zig+87-29| ... | ... | @@ -363,19 +363,24 @@ const Symbol = struct { |
| 363 | 363 | } |
| 364 | 364 | }; |
| 365 | 365 | |
| 366 | const built_in_ids = std.StaticStringMap(Inst.Ref).initComptime(.{ | |
| 367 | .{ "void", .void }, | |
| 368 | .{ "true", .bool_true }, | |
| 369 | .{ "false", .bool_false }, | |
| 370 | .{ "int", .int32_type }, | |
| 371 | .{ "float", .float_type }, | |
| 372 | .{ "string", .string_type }, | |
| 373 | .{ "opaque", .opaque_type }, | |
| 374 | // .{ "bigint", .bigint_type }, | |
| 375 | .{ "bool", .bool_type }, | |
| 376 | .{ "never", .never_type }, | |
| 377 | .{ "type", .type_type }, | |
| 378 | // .{ "std", .standard_library }, | |
| 366 | const BuiltIn = struct { | |
| 367 | type: Inst.Ref, | |
| 368 | value: Inst.Ref, | |
| 369 | }; | |
| 370 | ||
| 371 | const built_in_ids = std.StaticStringMap(BuiltIn).initComptime(.{ | |
| 372 | .{ "void", .{ .value = .void, .type = .void } }, | |
| 373 | .{ "true", .{ .value = .bool_true, .type = .bool_type } }, | |
| 374 | .{ "false", .{ .value = .bool_false, .type = .bool_type } }, | |
| 375 | .{ "int", .{ .value = .int32_type, .type = .type_type } }, | |
| 376 | .{ "float", .{ .value = .float_type, .type = .type_type } }, | |
| 377 | .{ "string", .{ .value = .string_type, .type = .type_type } }, | |
| 378 | .{ "opaque", .{ .value = .opaque_type, .type = .type_type } }, | |
| 379 | // .{ "bigint", .{ .value = .bigint_type, .type = .type_type } }, | |
| 380 | .{ "bool", .{ .value = .bool_type, .type = .type_type } }, | |
| 381 | .{ "never", .{ .value = .never_type, .type = .type_type } }, | |
| 382 | .{ "type", .{ .value = .type_type, .type = .type_type } }, | |
| 383 | // .{ "std", .{ .value = .standard_library, .type = ... } }, | |
| 379 | 384 | }); |
| 380 | 385 | |
| 381 | 386 | fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index { |
| ... | ... | @@ -940,8 +945,13 @@ fn transformExpression( |
| 940 | 945 | return .void; |
| 941 | 946 | } |
| 942 | 947 | |
| 943 | if (built_in_ids.get(id)) |ref| { | |
| 944 | return ref; | |
| 948 | if (built_in_ids.get(id)) |built_in| { | |
| 949 | // Elide the coercion when the type is known to be the type of the built-in | |
| 950 | if (result.* == .ref and result.ref == built_in.type) { | |
| 951 | result.* = .used; | |
| 952 | } | |
| 953 | ||
| 954 | return built_in.value; | |
| 945 | 955 | } |
| 946 | 956 | |
| 947 | 957 | const lookup = t.lookupId(id) orelse { |
| ... | ... | @@ -1048,8 +1058,6 @@ fn transformExpression( |
| 1048 | 1058 | .div, |
| 1049 | 1059 | .rem, |
| 1050 | 1060 | .pow, |
| 1051 | .@"and", | |
| 1052 | .@"or", | |
| 1053 | 1061 | .bit_and, |
| 1054 | 1062 | .bit_or, |
| 1055 | 1063 | .bit_xor, |
| ... | ... | @@ -1262,7 +1270,7 @@ fn transformExpression( |
| 1262 | 1270 | // .fetch => Builtin.fetch(t, decl, node, args), |
| 1263 | 1271 | .compile_error => Builtin.builtin_compileError(t, ctx, node, args), |
| 1264 | 1272 | .type_of => Builtin.builtin_typeOf(t, ctx, node, args), |
| 1265 | else => @panic("TODO"), | |
| 1273 | else => |tag| std.debug.panic("TODO builtin {s}", .{@tagName(tag)}), | |
| 1266 | 1274 | }; |
| 1267 | 1275 | }, |
| 1268 | 1276 | |
| ... | ... | @@ -1447,17 +1455,7 @@ fn transformExpression( |
| 1447 | 1455 | else |
| 1448 | 1456 | .void; |
| 1449 | 1457 | |
| 1450 | const coerced_operand = switch (break_result) { | |
| 1451 | .none, .used => operand, | |
| 1452 | else => try t.addRef(.coercion, .{ | |
| 1453 | .src_node = ctx.nodeOffset(node), | |
| 1454 | .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{ | |
| 1455 | .type = try break_result.consume(t) orelse | |
| 1456 | unreachable, // `.none` already handled | |
| 1457 | .operand = operand, | |
| 1458 | }), | |
| 1459 | }), | |
| 1460 | }; | |
| 1458 | const coerced_operand = try t.maybeCoerce(ctx.nodeOffset(node), operand, &break_result); | |
| 1461 | 1459 | |
| 1462 | 1460 | _ = try t.addIndex(.@"break", .{ |
| 1463 | 1461 | .src_node = ctx.nodeOffset(node), |
| ... | ... | @@ -1697,6 +1695,52 @@ fn transformExpression( |
| 1697 | 1695 | }); |
| 1698 | 1696 | }, |
| 1699 | 1697 | |
| 1698 | .bool_and => { | |
| 1699 | const bin = t.ast.nodeData(node).bin; | |
| 1700 | ||
| 1701 | var lhs_result: ResultType = .{ .ref = .bool_type }; | |
| 1702 | const lhs = try t.maybeCoerce( | |
| 1703 | ctx.nodeOffset(node), | |
| 1704 | try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse | |
| 1705 | return null, | |
| 1706 | &lhs_result, | |
| 1707 | ); | |
| 1708 | ||
| 1709 | const rhs = try t.transformToBlock(ctx, bin.rhs, .untracked_result, .{ .ref = .bool_type }, .run_time); | |
| 1710 | ||
| 1711 | return try t.addRef(.@"if", .{ | |
| 1712 | .src_node = ctx.nodeOffset(node), | |
| 1713 | .extra_index = try t.extra.append(t.gpa, Hir.If, .{ | |
| 1714 | .cond = lhs, | |
| 1715 | .yes = rhs, | |
| 1716 | .no = .bool_false, | |
| 1717 | }), | |
| 1718 | }); | |
| 1719 | }, | |
| 1720 | ||
| 1721 | .bool_or => { | |
| 1722 | const bin = t.ast.nodeData(node).bin; | |
| 1723 | ||
| 1724 | var lhs_result: ResultType = .{ .ref = .bool_type }; | |
| 1725 | const lhs = try t.maybeCoerce( | |
| 1726 | ctx.nodeOffset(node), | |
| 1727 | try t.transformExpression(ctx, &lhs_result, bin.lhs) orelse | |
| 1728 | return null, | |
| 1729 | &lhs_result, | |
| 1730 | ); | |
| 1731 | ||
| 1732 | const rhs = try t.transformToBlock(ctx, bin.rhs, .untracked_result, .{ .ref = .bool_type }, .run_time); | |
| 1733 | ||
| 1734 | return try t.addRef(.@"if", .{ | |
| 1735 | .src_node = ctx.nodeOffset(node), | |
| 1736 | .extra_index = try t.extra.append(t.gpa, Hir.If, .{ | |
| 1737 | .cond = lhs, | |
| 1738 | .yes = .bool_true, | |
| 1739 | .no = rhs, | |
| 1740 | }), | |
| 1741 | }); | |
| 1742 | }, | |
| 1743 | ||
| 1700 | 1744 | else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}), |
| 1701 | 1745 | } |
| 1702 | 1746 | } |
| ... | ... | @@ -2198,6 +2242,20 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData { |
| 2198 | 2242 | return &t.insts.items(.data)[@intFromEnum(index)]; |
| 2199 | 2243 | } |
| 2200 | 2244 | |
| 2245 | fn maybeCoerce(t: *Transform, node_offset: i32, operand: Inst.Ref, result: *ResultType) !Inst.Ref { | |
| 2246 | return switch (result.*) { | |
| 2247 | .none, .used => operand, | |
| 2248 | else => try t.addRef(.coercion, .{ | |
| 2249 | .src_node = node_offset, | |
| 2250 | .extra_index = try t.extra.append(t.gpa, Hir.Coercion, .{ | |
| 2251 | .type = try result.consume(t) orelse | |
| 2252 | unreachable, // `.none` already handled | |
| 2253 | .operand = operand, | |
| 2254 | }), | |
| 2255 | }), | |
| 2256 | }; | |
| 2257 | } | |
| 2258 | ||
| 2201 | 2259 | fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList { |
| 2202 | 2260 | const items = scratch_top.slice(u32); |
| 2203 | 2261 | const extra_top = t.extra.list.items.len; |
src/core/js/Link.zig+31-3| ... | ... | @@ -388,6 +388,12 @@ fn printExprLevel( |
| 388 | 388 | } |
| 389 | 389 | }, |
| 390 | 390 | |
| 391 | .stmt_throw => { | |
| 392 | const stmt: Tree.StmtThrow = tree.nodeData(.stmt_throw, node); | |
| 393 | try writer.writeAll("throw "); | |
| 394 | try link.printExpr(tree, stmt.operand, writer); | |
| 395 | }, | |
| 396 | ||
| 391 | 397 | .stmt_declare_local => { |
| 392 | 398 | const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node); |
| 393 | 399 | try writer.writeAll("let "); |
| ... | ... | @@ -435,10 +441,14 @@ fn printExprLevel( |
| 435 | 441 | .eq, |
| 436 | 442 | .lt, |
| 437 | 443 | .gt, |
| 444 | .ge, | |
| 445 | .le, | |
| 438 | 446 | .assign_add, |
| 439 | 447 | .assign_sub, |
| 440 | 448 | .assign_mul, |
| 441 | 449 | .assign_div, |
| 450 | .@"and", | |
| 451 | .@"or", | |
| 442 | 452 | => |tag| { |
| 443 | 453 | const bin: Tree.Bin = tree.nodeData(tag, node); |
| 444 | 454 | const op: Operator = @field(Operator, @tagName(tag)); |
| ... | ... | @@ -466,6 +476,14 @@ fn printExprLevel( |
| 466 | 476 | try writer.writeAll(")"); |
| 467 | 477 | }, |
| 468 | 478 | |
| 479 | .comma_chain => { | |
| 480 | const bin: Tree.IndexList = tree.nodeData(.comma_chain, node); | |
| 481 | for (bin.slice(tree), 0..) |expr, i| { | |
| 482 | if (i != 0) try writer.writeAll(", "); | |
| 483 | try link.printExprLevel(tree, expr, .assignment, writer); | |
| 484 | } | |
| 485 | }, | |
| 486 | ||
| 469 | 487 | inline .negate, |
| 470 | 488 | .bool_not, |
| 471 | 489 | .pre_dec, |
| ... | ... | @@ -483,7 +501,7 @@ fn printExprLevel( |
| 483 | 501 | const wrap = Level.conditional.needsParens(level); |
| 484 | 502 | if (wrap) |
| 485 | 503 | try writer.writeAll("("); |
| 486 | try link.printExpr(tree, ternary.cond, writer); | |
| 504 | try link.printExprLevel(tree, ternary.cond, .logical_or, writer); | |
| 487 | 505 | try writer.writeAll(" ? "); |
| 488 | 506 | try link.printExpr(tree, data.yes, writer); |
| 489 | 507 | try writer.writeAll(" : "); |
| ... | ... | @@ -788,10 +806,14 @@ const Operator = enum { |
| 788 | 806 | eq, |
| 789 | 807 | lt, |
| 790 | 808 | gt, |
| 809 | ge, | |
| 810 | le, | |
| 791 | 811 | negate, |
| 792 | 812 | bool_not, |
| 793 | 813 | pre_inc, |
| 794 | 814 | pre_dec, |
| 815 | @"and", | |
| 816 | @"or", | |
| 795 | 817 | |
| 796 | 818 | pub fn level(op: Operator) Level { |
| 797 | 819 | return switch (op) { |
| ... | ... | @@ -804,8 +826,9 @@ const Operator = enum { |
| 804 | 826 | .add, .sub => .additive, |
| 805 | 827 | .mul, .div => .multiplicative, |
| 806 | 828 | .eq => .equality, |
| 807 | .lt, .gt => .relational, | |
| 808 | ||
| 829 | .lt, .gt, .ge, .le => .relational, | |
| 830 | .@"and" => .logical_and, | |
| 831 | .@"or" => .logical_or, | |
| 809 | 832 | .pre_inc, |
| 810 | 833 | .pre_dec, |
| 811 | 834 | .bool_not, |
| ... | ... | @@ -828,6 +851,11 @@ const Operator = enum { |
| 828 | 851 | .eq => "===", |
| 829 | 852 | .lt => "<", |
| 830 | 853 | .gt => ">", |
| 854 | .le => "<=", | |
| 855 | .ge => ">=", | |
| 856 | ||
| 857 | .@"and" => "&&", | |
| 858 | .@"or" => "||", | |
| 831 | 859 | |
| 832 | 860 | .pre_inc => "++", |
| 833 | 861 | .pre_dec => "++", |
src/core/js/NodeGen.zig+117-10| ... | ... | @@ -157,6 +157,8 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 157 | 157 | inline .gt, |
| 158 | 158 | .lt, |
| 159 | 159 | .eq, |
| 160 | .ge, | |
| 161 | .le, | |
| 160 | 162 | .add, |
| 161 | 163 | .sub, |
| 162 | 164 | .mul, |
| ... | ... | @@ -250,6 +252,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 250 | 252 | try c.markExpression(inst, node); |
| 251 | 253 | }, |
| 252 | 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 | ||
| 253 | 277 | .field_opaque => { |
| 254 | 278 | const data: Air.Bin = c.air.instData(.field_opaque, inst); |
| 255 | 279 | if (data.rhs.value()) |field_value| { |
| ... | ... | @@ -336,8 +360,34 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void { |
| 336 | 360 | if (c.canLowerToExpression(extra.yes) and |
| 337 | 361 | c.canLowerToExpression(extra.no)) |
| 338 | 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 | ||
| 339 | 389 | const yes = try c.lowerToExpression(extra.yes); |
| 340 | const no = try c.lowerToExpression(extra.yes); | |
| 390 | const no = try c.lowerToExpression(extra.no); | |
| 341 | 391 | |
| 342 | 392 | const ternary = try c.add(.ternary, .{ |
| 343 | 393 | .cond = cond, |
| ... | ... | @@ -559,7 +609,7 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index { |
| 559 | 609 | switch (flags.uses) { |
| 560 | 610 | .none => { |
| 561 | 611 | // incorrectly marked instruction as unused |
| 562 | std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{ | |
| 612 | std.debug.panic("called fetchInstExpression(#{d}), but it was marked as unused", .{ | |
| 563 | 613 | @intFromEnum(index), |
| 564 | 614 | }); |
| 565 | 615 | }, |
| ... | ... | @@ -722,7 +772,6 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { |
| 722 | 772 | const next_label = c.next_label; |
| 723 | 773 | |
| 724 | 774 | try c.scratch.appendSlice(c.arena, &.{ |
| 725 | // @intFromEnum(if (temporary) |temp| temp.toOptional() else .none), | |
| 726 | 775 | @intFromEnum(Node.Index.Optional.none), |
| 727 | 776 | next_label, |
| 728 | 777 | }); |
| ... | ... | @@ -749,6 +798,46 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void { |
| 749 | 798 | })); |
| 750 | 799 | } |
| 751 | 800 | |
| 801 | fn codeGenBlockAsExpression(c: *NodeGen, inst: Air.Inst.Index) !Node.Index { | |
| 802 | const t = tracer.trace(@src()); | |
| 803 | defer t.end(); | |
| 804 | ||
| 805 | const scratch_top = c.scratch.items.len; | |
| 806 | errdefer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 807 | ||
| 808 | try c.scratch.appendSlice(c.arena, &.{ | |
| 809 | @intFromEnum(Node.Index.Optional.none), | |
| 810 | 0, | |
| 811 | }); | |
| 812 | ||
| 813 | c.air_node_map[@intFromEnum(inst)] = .{ | |
| 814 | .block = .{ .index = @intCast(scratch_top) }, | |
| 815 | }; | |
| 816 | const block: Air.IndexList = c.air.instData(.block, inst); | |
| 817 | const instructions = block.slice(&c.air); | |
| 818 | for (instructions[0 .. instructions.len - 1]) |item| { | |
| 819 | try c.codeGenInst(item); | |
| 820 | } | |
| 821 | ||
| 822 | const last_break: Air.Break = c.air.instData(.@"break", instructions[instructions.len - 1]); | |
| 823 | const last_break_operand = try c.fetchExpression(last_break.value); | |
| 824 | ||
| 825 | try c.scratch.append(c.arena, @intFromEnum(last_break_operand)); | |
| 826 | ||
| 827 | const items = c.scratch.items[scratch_top + 2 ..]; | |
| 828 | const top = c.extra.list.items.len; | |
| 829 | defer c.scratch.shrinkRetainingCapacity(scratch_top); | |
| 830 | assert(items.len > 0); | |
| 831 | if (items.len == 1) { | |
| 832 | return @enumFromInt(items[0]); | |
| 833 | } | |
| 834 | try c.extra.list.appendSlice(c.gpa, items); | |
| 835 | return try c.add(.comma_chain, .{ | |
| 836 | .start = @intCast(top), | |
| 837 | .len = @intCast(items.len), | |
| 838 | }); | |
| 839 | } | |
| 840 | ||
| 752 | 841 | // Ideally, we convert everything to an expression. This allows for much |
| 753 | 842 | // more compact code. When that isnt possible (`return`, `while`, etc), |
| 754 | 843 | // we detect that here and ensure to lower `if` to actual if statements, |
| ... | ... | @@ -774,6 +863,8 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 774 | 863 | .eq, |
| 775 | 864 | .lt, |
| 776 | 865 | .gt, |
| 866 | .ge, | |
| 867 | .le, | |
| 777 | 868 | .negate, |
| 778 | 869 | .bool_not, |
| 779 | 870 | .store, |
| ... | ... | @@ -787,15 +878,28 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool { |
| 787 | 878 | // Later, this could be made true if we can hoist temporaries up. |
| 788 | 879 | .memory => false, |
| 789 | 880 | |
| 790 | // break/return must be a js statement | |
| 791 | .@"break", | |
| 792 | => false, | |
| 881 | // these must be lowered to js statements | |
| 882 | .@"break", .panic => false, | |
| 793 | 883 | |
| 884 | // A block can become a comma expression if all expressions | |
| 885 | // are lowerable. The break at the end just becomes the result. | |
| 794 | 886 | .block => { |
| 795 | // TODO: blocks that are | |
| 796 | // - all statements except last are lowerable | |
| 797 | // - the last statement is lowerable OR a break of this block | |
| 798 | return false; | |
| 887 | const block: Air.IndexList = c.air.instData(.block, index); | |
| 888 | const items = block.slice(&c.air); | |
| 889 | for (items[0 .. items.len - 1]) |item| { | |
| 890 | if (c.air.instFlags(item).uses == .multi) | |
| 891 | return false; | |
| 892 | if (!c.canLowerToExpression(item.toRef())) | |
| 893 | return false; | |
| 894 | } | |
| 895 | const last_item = items[items.len - 1]; | |
| 896 | if (c.air.instTag(last_item) != .@"break") { | |
| 897 | return c.canLowerToExpression(last_item.toRef()); | |
| 898 | } | |
| 899 | const brk = c.air.instData(.@"break", last_item); | |
| 900 | // The last break will only be lowerable if it | |
| 901 | // breaks out of this specific block | |
| 902 | return brk.block == index; | |
| 799 | 903 | }, |
| 800 | 904 | |
| 801 | 905 | .@"if" => { |
| ... | ... | @@ -818,6 +922,9 @@ fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index { |
| 818 | 922 | return switch (ref.decode()) { |
| 819 | 923 | .value => |v| try c.valueToExpression(v), |
| 820 | 924 | .index => |index| { |
| 925 | if (c.air.instTag(index) == .block) { | |
| 926 | return try c.codeGenBlockAsExpression(index); | |
| 927 | } | |
| 821 | 928 | try c.codeGenInst(index); |
| 822 | 929 | return c.fetchExpression(ref); |
| 823 | 930 | }, |
src/core/js/Tree.zig+18| ... | ... | @@ -39,6 +39,10 @@ pub const Tag = enum { |
| 39 | 39 | eq, |
| 40 | 40 | lt, |
| 41 | 41 | gt, |
| 42 | ge, | |
| 43 | le, | |
| 44 | @"and", | |
| 45 | @"or", | |
| 42 | 46 | // Exprs |
| 43 | 47 | call, |
| 44 | 48 | js_new, |
| ... | ... | @@ -47,6 +51,7 @@ pub const Tag = enum { |
| 47 | 51 | object_literal, |
| 48 | 52 | array, |
| 49 | 53 | index, |
| 54 | comma_chain, | |
| 50 | 55 | // Prefix ops |
| 51 | 56 | negate, |
| 52 | 57 | bool_not, |
| ... | ... | @@ -63,6 +68,8 @@ pub const Tag = enum { |
| 63 | 68 | stmt_return, |
| 64 | 69 | /// `...: { ... }` |
| 65 | 70 | stmt_block, |
| 71 | /// `throw x` | |
| 72 | stmt_throw, | |
| 66 | 73 | |
| 67 | 74 | pub fn Data(comptime tag: Tag) type { |
| 68 | 75 | return switch (tag) { |
| ... | ... | @@ -83,7 +90,11 @@ pub const Tag = enum { |
| 83 | 90 | .eq, |
| 84 | 91 | .lt, |
| 85 | 92 | .gt, |
| 93 | .ge, | |
| 94 | .le, | |
| 86 | 95 | .index, |
| 96 | .@"and", | |
| 97 | .@"or", | |
| 87 | 98 | => Bin, |
| 88 | 99 | .ternary => Ternary, |
| 89 | 100 | .negate, |
| ... | ... | @@ -95,9 +106,11 @@ pub const Tag = enum { |
| 95 | 106 | .array => IndexList, |
| 96 | 107 | .call, .js_new => Call, |
| 97 | 108 | .field => Field, |
| 109 | .comma_chain => IndexList, | |
| 98 | 110 | .stmt_if => StmtIf, |
| 99 | 111 | .stmt_break => StmtBreak, |
| 100 | 112 | .stmt_return => StmtReturn, |
| 113 | .stmt_throw => StmtThrow, | |
| 101 | 114 | .stmt_declare_local => StmtDeclareLocal, |
| 102 | 115 | .stmt_block => StmtBlock, |
| 103 | 116 | }; |
| ... | ... | @@ -261,6 +274,11 @@ pub const StmtReturn = struct { |
| 261 | 274 | unused: Unused = .zero, |
| 262 | 275 | }; |
| 263 | 276 | |
| 277 | pub const StmtThrow = struct { | |
| 278 | operand: Node.Index, | |
| 279 | unused: Unused = .zero, | |
| 280 | }; | |
| 281 | ||
| 264 | 282 | pub const StmtBlock = struct { |
| 265 | 283 | label: u32, |
| 266 | 284 | content: ExtraIndex(IndexList), |
src/deps/quickjs/build.zig+2-2| ... | ... | @@ -136,7 +136,7 @@ pub fn build(b: *Build) !void { |
| 136 | 136 | .optimize = optimize, |
| 137 | 137 | .link_libc = true, |
| 138 | 138 | }); |
| 139 | if (pthread.pthreads) |pt| translate.addIncludeDir(pt.include.getPath(b)); | |
| 139 | if (pthread.pthreads) |pt| translate.addIncludePath(pt.include); | |
| 140 | 140 | const module = translate.addModule("quickjs"); |
| 141 | 141 | module.linkLibrary(libquickjs); |
| 142 | 142 | |
| ... | ... | @@ -147,7 +147,7 @@ pub fn build(b: *Build) !void { |
| 147 | 147 | .link_libc = true, |
| 148 | 148 | }); |
| 149 | 149 | const module2 = translate_libc.addModule("quickjs-libc"); |
| 150 | if (pthread.pthreads) |pt| translate_libc.addIncludeDir(pt.include.getPath(b)); | |
| 150 | if (pthread.pthreads) |pt| translate_libc.addIncludePath(pt.include); | |
| 151 | 151 | module2.linkLibrary(libquickjs); |
| 152 | 152 | module2.linkLibrary(libquickjs_libc); |
| 153 | 153 | } |
test/behavior/array+2-2| ... | ... | @@ -1,8 +1,7 @@ |
| 1 | 1 | let assertEqual = @extern("assertEqual"); |
| 2 | 2 | |
| 3 | 3 | fn main { |
| 4 | let array = [i(9), i(3), i(8), i(3), i(-1)]; | |
| 5 | ||
| 4 | let array = a([9, 3, 8, 3, -1]); | |
| 6 | 5 | assertEqual(array.len, i(5)); |
| 7 | 6 | assertEqual(array[0], i(9)); |
| 8 | 7 | assertEqual(array[1], i(3)); |
| ... | ... | @@ -12,3 +11,4 @@ fn main { |
| 12 | 11 | } |
| 13 | 12 | |
| 14 | 13 | fn i(a: int) { a } |
| 14 | fn a(a: []int) { a } |
test/compiler_tests.zig+1-1| ... | ... | @@ -506,7 +506,7 @@ pub fn panic(msg: []const u8, trace: ?*const std.builtin.StackTrace, first_trace |
| 506 | 506 | if (current_case) |case| { |
| 507 | 507 | std.debug.print("Crash during case: {s}\n", .{case.display}); |
| 508 | 508 | } |
| 509 | std.debug.panicImpl(trace, first_trace_addr, msg); | |
| 509 | std.debug.defaultPanic(msg, trace, first_trace_addr); | |
| 510 | 510 | } |
| 511 | 511 | |
| 512 | 512 | const std = @import("std"); |