authorchloe caruso <git@paperclover.net> 2024-10-27 01:50:09-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-10-27 01:50:09-07:00
logfd9bc5492894391c36ce4ee485e103ceb0255f76
treeafb74799a604f6c032ed06791f284a138b15ce67
parent230648717c1f197ece3aec476cda99ad3fa158a6

feat: safety check code (#121)

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,11 +2,14 @@
22
3- chore: rename the language to "ix"3- chore: rename the language to "ix"
4- feat: language manual gets a pleasing redesign4- feat: language manual gets a pleasing redesign
5- feat: add boolean `and` and `or` operator keywords
5- feat: allow any expression as a function body6- feat: allow any expression as a function body
6- feat: add `assert` keyword7- feat: add `assert` keyword
8- feat: add safety checks for array out of bounds
7- fix: coerce `break` operands and implicit breaks to result type9- fix: coerce `break` operands and implicit breaks to result type
8- fix: coerce results when `if` expression branches merge10- fix: coerce results when `if` expression branches merge
9- fix: ensure if branch results to a runtime value when the condition is not compile-time known11- 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- fix(js): stop printing redundant parenthesis on ternary expressions13- fix(js): stop printing redundant parenthesis on ternary expressions
1114
12## 0.1.615## 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,7 +594,8 @@ fn crossTarget(cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag) Build.R
594 };594 };
595 return .{595 return .{
596 .query = query,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}
600601
build.zig.zon+1-1
...@@ -2,7 +2,7 @@...@@ -2,7 +2,7 @@
2 .name = "",2 .name = "",
3 .version = "0.0.0",3 .version = "0.0.0",
44
5 .minimum_zig_version = "0.14.0-dev.1511+54b668f8a",5 .minimum_zig_version = "0.14.0-dev.2034+56996a280",
66
7 .dependencies = .{7 .dependencies = .{
8 // Used to determine unicode properties to tokenize identifiers8 // Used to determine unicode properties to tokenize identifiers
src/core/Air.Dump.zig+3
...@@ -87,6 +87,8 @@ pub const State = struct {...@@ -87,6 +87,8 @@ pub const State = struct {
87 .lt,87 .lt,
88 .gt,88 .gt,
89 .eq,89 .eq,
90 .le,
91 .ge,
90 .field_array,92 .field_array,
91 .field_opaque,93 .field_opaque,
92 => |comptime_tag| {94 => |comptime_tag| {
...@@ -100,6 +102,7 @@ pub const State = struct {...@@ -100,6 +102,7 @@ pub const State = struct {
100 inline .negate,102 inline .negate,
101 .bool_not,103 .bool_not,
102 .array_len,104 .array_len,
105 .panic,
103 => |comptime_tag| {106 => |comptime_tag| {
104 const bin: Air.Un = air.instData(comptime_tag, inst);107 const bin: Air.Un = air.instData(comptime_tag, inst);
105 try w.writeAll("(");108 try w.writeAll("(");
src/core/Air.zig+10-6
...@@ -34,20 +34,19 @@ pub const Inst = struct {...@@ -34,20 +34,19 @@ pub const Inst = struct {
3434
35 array_len,35 array_len,
3636
37 // bin
38 add,37 add,
39 sub,38 sub,
40 mul,39 mul,
41 div,40 div,
42
43 eq,41 eq,
44
45 lt,42 lt,
43 le,
46 gt,44 gt,
45 ge,
4746
48 // un
49 negate,47 negate,
50 bool_not,48 bool_not,
49 panic,
5150
52 pub fn Data(comptime tag: Tag) type {51 pub fn Data(comptime tag: Tag) type {
53 return switch (tag) {52 return switch (tag) {
...@@ -71,10 +70,13 @@ pub const Inst = struct {...@@ -71,10 +70,13 @@ pub const Inst = struct {
71 .lt,70 .lt,
72 .gt,71 .gt,
73 .eq,72 .eq,
73 .le,
74 .ge,
74 => Bin,75 => Bin,
75 .negate,76 .negate,
76 .bool_not,77 .bool_not,
77 .array_len,78 .array_len,
79 .panic,
78 => Un,80 => Un,
79 };81 };
80 }82 }
...@@ -85,6 +87,8 @@ pub const Inst = struct {...@@ -85,6 +87,8 @@ pub const Inst = struct {
85 kind: Kind,87 kind: Kind,
86 data: u31,88 data: u31,
8789
90 pub const @"void" = Value.void.toAir();
91
88 pub const Kind = enum(u1) { value, index };92 pub const Kind = enum(u1) { value, index };
8993
90 pub const Decoded = union(enum) {94 pub const Decoded = union(enum) {
...@@ -345,8 +349,8 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In...@@ -345,8 +349,8 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
345 .js_new => .@"opaque",349 .js_new => .@"opaque",
346350
347 // These operations always produce known types351 // These operations always produce known types
348 .lt, .gt, .eq, .bool_not => .bool,352 .lt, .gt, .le, .ge, .eq, .bool_not => .bool,
349 .@"break" => .never,353 .@"break", .panic => .never,
350 .field_opaque => .@"opaque",354 .field_opaque => .@"opaque",
351 .array_len => .int32,355 .array_len => .int32,
352356
src/core/Ast.zig+6-6
...@@ -144,9 +144,9 @@ pub const Node = struct {...@@ -144,9 +144,9 @@ pub const Node = struct {
144 /// `lhs ** rhs`. main_token is the operator.144 /// `lhs ** rhs`. main_token is the operator.
145 pow,145 pow,
146 /// `lhs && rhs`. main_token is the operator.146 /// `lhs && rhs`. main_token is the operator.
147 @"and",147 bool_and,
148 /// `lhs || rhs`. main_token is the operator.148 /// `lhs || rhs`. main_token is the operator.
149 @"or",149 bool_or,
150 /// `lhs & rhs`. main_token is the operator.150 /// `lhs & rhs`. main_token is the operator.
151 bit_and,151 bit_and,
152 /// `lhs | rhs`. main_token is the operator.152 /// `lhs | rhs`. main_token is the operator.
...@@ -247,8 +247,8 @@ pub const Node = struct {...@@ -247,8 +247,8 @@ pub const Node = struct {
247 .div,247 .div,
248 .rem,248 .rem,
249 .pow,249 .pow,
250 .@"and",250 .bool_and,
251 .@"or",251 .bool_or,
252 .bit_and,252 .bit_and,
253 .bit_or,253 .bit_or,
254 .bit_xor,254 .bit_xor,
...@@ -280,11 +280,11 @@ pub const Node = struct {...@@ -280,11 +280,11 @@ pub const Node = struct {
280280
281 pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{281 pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{
282 // sort-lines:start282 // sort-lines:start
283 .@"and" = .bin,283 .bool_and = .bin,
284 .@"break" = .@"break",284 .@"break" = .@"break",
285 .@"comptime" = .un,285 .@"comptime" = .un,
286 .@"if" = .@"if",286 .@"if" = .@"if",
287 .@"or" = .bin,287 .bool_or = .bin,
288 .@"return" = .@"return",288 .@"return" = .@"return",
289 .add = .bin,289 .add = .bin,
290 .add_assign = .bin,290 .add_assign = .bin,
src/core/Hir.Dump.zig+1-1
...@@ -307,7 +307,7 @@ const State = struct {...@@ -307,7 +307,7 @@ const State = struct {
307307
308 if (data.type_hint.unwrap()) |type_hint| {308 if (data.type_hint.unwrap()) |type_hint| {
309 try writer.writeAll("(");309 try writer.writeAll("(");
310 try s.dumpRef(writer, type_hint);310 try s.dumpRefMarker(writer, type_hint);
311 try writer.writeAll(", ");311 try writer.writeAll(", ");
312 }312 }
313313
src/core/Hir.zig+5-2
...@@ -60,9 +60,12 @@ pub const Tag = enum(u8) {...@@ -60,9 +60,12 @@ pub const Tag = enum(u8) {
60 /// Asserts the operand is `void`. Used for undiscarded expressions.60 /// Asserts the operand is `void`. Used for undiscarded expressions.
61 ensure_void,61 ensure_void,
6262
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 @"if",66 @"if",
65 /// Given a block instruction67 /// Given a block instruction, evaluate to a Value without generating
68 /// any runtime instructions.
66 @"comptime",69 @"comptime",
6770
68 /// A function expression, with or without captures71 /// A function expression, with or without captures
src/core/Parse.zig+15-14
...@@ -111,8 +111,8 @@ const Operator = enum(u8) {...@@ -111,8 +111,8 @@ const Operator = enum(u8) {
111 div = @intFromEnum(Token.Tag.slash),111 div = @intFromEnum(Token.Tag.slash),
112 rem = @intFromEnum(Token.Tag.percent),112 rem = @intFromEnum(Token.Tag.percent),
113 pow = @intFromEnum(Token.Tag.star_star),113 pow = @intFromEnum(Token.Tag.star_star),
114 @"and" = @intFromEnum(Token.Tag.and_and),114 bool_and = @intFromEnum(Token.Tag.kw_and),
115 @"or" = @intFromEnum(Token.Tag.pipe_pipe),115 bool_or = @intFromEnum(Token.Tag.kw_or),
116 bit_and = @intFromEnum(Token.Tag.@"and"),116 bit_and = @intFromEnum(Token.Tag.@"and"),
117 bit_or = @intFromEnum(Token.Tag.pipe),117 bit_or = @intFromEnum(Token.Tag.pipe),
118 bit_xor = @intFromEnum(Token.Tag.caret),118 bit_xor = @intFromEnum(Token.Tag.caret),
...@@ -209,8 +209,8 @@ const Operator = enum(u8) {...@@ -209,8 +209,8 @@ const Operator = enum(u8) {
209 .shl, .shr => .bitwise_shift,209 .shl, .shr => .bitwise_shift,
210 .lt, .le, .gt, .ge => .relational,210 .lt, .le, .gt, .ge => .relational,
211 .eq, .ne => .equality,211 .eq, .ne => .equality,
212 .@"and" => .logical_and,212 .bool_and => .logical_and,
213 .@"or" => .logical_or,213 .bool_or => .logical_or,
214 .bit_and => .bit_and,214 .bit_and => .bit_and,
215 .bit_xor => .bit_xor,215 .bit_xor => .bit_xor,
216 .bit_or => .bit_or,216 .bit_or => .bit_or,
...@@ -1493,14 +1493,15 @@ const Comp = core.Comp;...@@ -1493,14 +1493,15 @@ const Comp = core.Comp;
14931493
1494test "fuzz test parser for crashes" {1494test "fuzz test parser for crashes" {
1495 try core.unicode_props.init();1495 try core.unicode_props.init();
14961496 try std.testing.fuzz(struct {
1497 const input_bytes = std.testing.fuzzInput(.{});1497 fn run(input_bytes: []const u8) !void {
14981498 const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes);
1499 const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes);1499 defer std.testing.allocator.free(sentinel);
1500 defer std.testing.allocator.free(sentinel);1500
15011501 var err: AstError.WithTrace = undefined;
1502 var err: AstError.WithTrace = undefined;1502 var ast = parse(std.testing.allocator, sentinel, &err) catch
1503 var ast = parse(std.testing.allocator, sentinel, &err) catch1503 return; // ignore parser errors, only fail on crashes
1504 return; // ignore parser errors, only fail on crashes1504 ast.deinit(std.testing.allocator);
1505 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,6 +13,8 @@ arena_state: std.heap.ArenaAllocator,
13/// When this is .decl, we are producing a `Value`13/// When this is .decl, we are producing a `Value`
14owner: AnalysisUnit,14owner: AnalysisUnit,
1515
16generate_safety_checks: bool = true,
17
16/// The namespace index that is used for declaration lookups18/// The namespace index that is used for declaration lookups
17ns_index: Namespace.Index,19ns_index: Namespace.Index,
18/// Non-owning copy20/// Non-owning copy
...@@ -358,8 +360,9 @@ fn markInst(s: *Sema, inst: Air.Inst.Index) void {...@@ -358,8 +360,9 @@ fn markInst(s: *Sema, inst: Air.Inst.Index) void {
358 flags.uses = flags.uses.increment();360 flags.uses = flags.uses.increment();
359}361}
360362
361// TODO: name when awaake pls363const LivenessMark = enum {
362const DoMark = enum {364 /// The value retrieved will not be used as the operand for another
365 /// instruction.
363 no_mark,366 no_mark,
364 /// A Ref must be marked if it's value is used as the operand for another367 /// A Ref must be marked if it's value is used as the operand for another
365 /// Air instruction. This informs the Js backend if an expression must368 /// Air instruction. This informs the Js backend if an expression must
...@@ -367,7 +370,7 @@ const DoMark = enum {...@@ -367,7 +370,7 @@ const DoMark = enum {
367 marked,370 marked,
368};371};
369372
370fn getRef(s: *Sema, teg_ref: Hir.Inst.Ref, mark: DoMark) Air.Inst.Ref {373fn getRef(s: *Sema, teg_ref: Hir.Inst.Ref, mark: LivenessMark) Air.Inst.Ref {
371 switch (teg_ref.decode()) {374 switch (teg_ref.decode()) {
372 .value => |value| return value.toAir(),375 .value => |value| return value.toAir(),
373 .index => |index| {376 .index => |index| {
...@@ -400,7 +403,7 @@ fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type) !Va...@@ -400,7 +403,7 @@ fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type) !Va
400 return try s.coerceValue(src, any, ty);403 return try s.coerceValue(src, any, ty);
401}404}
402405
403fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type, mark: DoMark) !Air.Inst.Ref {406fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Hir.Inst.Ref, ty: Type, mark: LivenessMark) !Air.Inst.Ref {
404 const any = s.getRef(teg_ref, mark);407 const any = s.getRef(teg_ref, mark);
405 return try s.coerceRef(src, any, ty);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,6 +622,8 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
619 });622 });
620 }623 }
621624
625 try s.ensureAtRuntime(s.lazyNode(.operand));
626
622 return s.addRef(.negate, .{ .operand = operand });627 return s.addRef(.negate, .{ .operand = operand });
623 },628 },
624 .bool_not => {629 .bool_not => {
...@@ -799,10 +804,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)...@@ -799,10 +804,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
799 const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, node.list.len);804 const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, node.list.len);
800 defer scratch.deinit();805 defer scratch.deinit();
801806
802 var item_type: ?Type = if (node.type_hint.unwrap()) |ty|807 var item_type: ?Type = if (node.type_hint.unwrap()) |ty| inferred_type: {
803 try s.getType(s.lazyNode(.operand), ty)808 const parent_type = try s.getType(s.lazyNode(.operand), ty);
804 else809 const array_data: InternPool.Key.ArrayType = parent_type.toArrayType(s.ip) orelse
805 null;810 // Allow the coercion to fail later.
811 break :inferred_type null;
812 break :inferred_type array_data.item_type;
813 } else null;
806814
807 var is_constant = true;815 var is_constant = true;
808816
...@@ -1103,13 +1111,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)...@@ -1103,13 +1111,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
11031111
1104 switch (data.conditional) {1112 switch (data.conditional) {
1105 .comptime_condition => {1113 .comptime_condition => {
1106 assert(operand.kind == .value); // must be comptime
1107 s.inst_map.add(s.frame.currentIndex(), operand);1114 s.inst_map.add(s.frame.currentIndex(), operand);
1108 s.frame.advance();1115 s.frame.advance();
1109 return .@"continue";1116 return .@"continue";
1110 },1117 },
1111 .runtime_yes => {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 mark.deinit();1120 mark.deinit();
11141121
1115 switch (if_data.no.decode()) {1122 switch (if_data.no.decode()) {
...@@ -1127,7 +1134,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)...@@ -1127,7 +1134,7 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
1127 }1134 }
1128 },1135 },
1129 .runtime_no => {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 mark.deinit();1138 mark.deinit();
1132 },1139 },
1133 }1140 }
...@@ -1140,6 +1147,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)...@@ -1140,6 +1147,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
1140 if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir();1147 if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir();
1141 if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir();1148 if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir();
11421149
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 const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) });1156 const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) });
11441157
1145 const yes_coerced = try s.coerceBranch(s.lazyNode(.lhs), yes, union_type);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,11 +1179,15 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
11661179
1167 const return_type = switch (data.func) {1180 const return_type = switch (data.func) {
1168 .argument => |arg_num| ret: {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 for (1187 for (
1170 func_data.params.slice(&s.hir)[arg_num + 1 ..],1188 func_data.params.slice(&s.hir)[arg_num + 1 ..],
1171 reserve.items(.params)[arg_num + 1 ..],
1172 arg_num + 1..,1189 arg_num + 1..,
1173 ) |param, *dst, i| {1190 ) |param, i| {
1174 const known = param.unwrap() orelse @panic("TODO: inferred param sets");1191 const known = param.unwrap() orelse @panic("TODO: inferred param sets");
1175 switch (known.decode()) {1192 switch (known.decode()) {
1176 .value => |val| {1193 .value => |val| {
...@@ -1181,7 +1198,6 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)...@@ -1181,7 +1198,6 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
1181 .type = ty,1198 .type = ty,
1182 });1199 });
1183 }1200 }
1184 dst.* = ty;
1185 },1201 },
1186 .index => |block| {1202 .index => |block| {
1187 const mode = s.frame.mode;1203 const mode = s.frame.mode;
...@@ -1225,6 +1241,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)...@@ -1225,6 +1241,13 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
1225 },1241 },
1226 };1242 };
12271243
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 try s.postVisitFunctionPrototype(reserve, return_type);1251 try s.postVisitFunctionPrototype(reserve, return_type);
1229 return .@"continue";1252 return .@"continue";
1230 },1253 },
...@@ -1319,6 +1342,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)...@@ -1319,6 +1342,12 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
1319 const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type);1342 const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type);
1320 const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type);1343 const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type);
13211344
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 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{1351 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{
1323 .cond = condition,1352 .cond = condition,
1324 .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{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,39 +1602,54 @@ fn stepInner(s: *Sema, comptime tag: Hir.Tag) Error!InstructionKind.fromTag(tag)
1573 }1602 }
1574}1603}
15751604
1576fn emitRuntimeBlock(1605fn emitRuntimeBranchForIf(
1577 s: *Sema,1606 s: *Sema,
1578 from: Hir.Inst.Index,1607 from: Hir.Inst.Index,
1579 break_operand: Air.Inst.Ref,1608 result_value: Air.Inst.Ref,
1580 mark: core.U32Stack.Mark,1609 mark: core.U32Stack.Mark,
1581) !void {1610) !void {
1582 s.inst_map.add(1611 s.inst_map.add(
1583 from,1612 from,
1584 if (mark.len() == 0) brk: {1613 if (mark.len() == 0 and result_value.kind == .value) brk: {
1585 assert(break_operand.kind == .value);1614 break :brk result_value;
1586 break :brk break_operand;1615 } else (try s.createRuntimeBlock(mark, result_value)).toRef(),
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 },
1605 );1616 );
1606 mark.deinit();1617 mark.deinit();
1607}1618}
16081619
1620fn 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
1609const BinaryOp = enum {1653const BinaryOp = enum {
1610 add,1654 add,
1611 sub,1655 sub,
...@@ -1889,7 +1933,8 @@ fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst....@@ -1889,7 +1933,8 @@ fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst.
1889 return switch (ref.decode()) {1933 return switch (ref.decode()) {
1890 .index => |block| {1934 .index => |block| {
1891 if (s.typeOfIndex(block) != to) return ref;1935 if (s.typeOfIndex(block) != to) return ref;
1892 @panic("TODO");1936 // @panic("TODO");
1937 return ref;
1893 },1938 },
1894 .value => |value| (try s.coerceValue(src, value, to)).toAir(),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,6 +2270,38 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In
22252270
2226 try s.ensureAtRuntime(s.lazyNode(.main_node));2271 try s.ensureAtRuntime(s.lazyNode(.main_node));
22272272
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 return s.addRef(.field_array, .{2305 return s.addRef(.field_array, .{
2229 .lhs = lhs,2306 .lhs = lhs,
2230 .rhs = index,2307 .rhs = index,
...@@ -2233,6 +2310,17 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In...@@ -2233,6 +2310,17 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Hir.In
2233 }2310 }
2234}2311}
22352312
2313fn 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
2236fn fieldAccessTuple(2324fn fieldAccessTuple(
2237 s: *Sema,2325 s: *Sema,
2238 lhs: Air.Inst.Ref,2326 lhs: Air.Inst.Ref,
...@@ -2297,6 +2385,27 @@ fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ?...@@ -2297,6 +2385,27 @@ fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ?
2297 try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } });2385 try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } });
2298}2386}
22992387
2388fn 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//
23012410
2302fn tempAir(s: *const Sema) Air {2411fn 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,6 +2451,12 @@ fn addRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref
2342 return (try s.addIndex(tag, data)).toRef();2451 return (try s.addIndex(tag, data)).toRef();
2343}2452}
23442453
2454fn 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
2345fn typeOfIndex(s: *Sema, i: Air.Inst.Index) Type {2460fn typeOfIndex(s: *Sema, i: Air.Inst.Index) Type {
2346 return s.tempAir().typeOfIndex(i, s.ip);2461 return s.tempAir().typeOfIndex(i, s.ip);
2347}2462}
src/core/Tokenizer.zig+2
...@@ -55,6 +55,7 @@ pub const Token = struct {...@@ -55,6 +55,7 @@ pub const Token = struct {
55 module_doc_comment,55 module_doc_comment,
5656
57 // sort-lines: start57 // sort-lines: start
58 kw_and,
58 kw_assert,59 kw_assert,
59 kw_break,60 kw_break,
60 kw_comptime,61 kw_comptime,
...@@ -70,6 +71,7 @@ pub const Token = struct {...@@ -70,6 +71,7 @@ pub const Token = struct {
70 kw_let,71 kw_let,
71 kw_match,72 kw_match,
72 kw_module,73 kw_module,
74 kw_or,
73 kw_pub,75 kw_pub,
74 kw_pure,76 kw_pure,
75 kw_return,77 kw_return,
src/core/Transform.zig+87-29
...@@ -363,19 +363,24 @@ const Symbol = struct {...@@ -363,19 +363,24 @@ const Symbol = struct {
363 }363 }
364};364};
365365
366const built_in_ids = std.StaticStringMap(Inst.Ref).initComptime(.{366const BuiltIn = struct {
367 .{ "void", .void },367 type: Inst.Ref,
368 .{ "true", .bool_true },368 value: Inst.Ref,
369 .{ "false", .bool_false },369};
370 .{ "int", .int32_type },370
371 .{ "float", .float_type },371const built_in_ids = std.StaticStringMap(BuiltIn).initComptime(.{
372 .{ "string", .string_type },372 .{ "void", .{ .value = .void, .type = .void } },
373 .{ "opaque", .opaque_type },373 .{ "true", .{ .value = .bool_true, .type = .bool_type } },
374 // .{ "bigint", .bigint_type },374 .{ "false", .{ .value = .bool_false, .type = .bool_type } },
375 .{ "bool", .bool_type },375 .{ "int", .{ .value = .int32_type, .type = .type_type } },
376 .{ "never", .never_type },376 .{ "float", .{ .value = .float_type, .type = .type_type } },
377 .{ "type", .type_type },377 .{ "string", .{ .value = .string_type, .type = .type_type } },
378 // .{ "std", .standard_library },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});
380385
381fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index {386fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index {
...@@ -940,8 +945,13 @@ fn transformExpression(...@@ -940,8 +945,13 @@ fn transformExpression(
940 return .void;945 return .void;
941 }946 }
942947
943 if (built_in_ids.get(id)) |ref| {948 if (built_in_ids.get(id)) |built_in| {
944 return ref;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 }
946956
947 const lookup = t.lookupId(id) orelse {957 const lookup = t.lookupId(id) orelse {
...@@ -1048,8 +1058,6 @@ fn transformExpression(...@@ -1048,8 +1058,6 @@ fn transformExpression(
1048 .div,1058 .div,
1049 .rem,1059 .rem,
1050 .pow,1060 .pow,
1051 .@"and",
1052 .@"or",
1053 .bit_and,1061 .bit_and,
1054 .bit_or,1062 .bit_or,
1055 .bit_xor,1063 .bit_xor,
...@@ -1262,7 +1270,7 @@ fn transformExpression(...@@ -1262,7 +1270,7 @@ fn transformExpression(
1262 // .fetch => Builtin.fetch(t, decl, node, args),1270 // .fetch => Builtin.fetch(t, decl, node, args),
1263 .compile_error => Builtin.builtin_compileError(t, ctx, node, args),1271 .compile_error => Builtin.builtin_compileError(t, ctx, node, args),
1264 .type_of => Builtin.builtin_typeOf(t, ctx, node, args),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 },
12681276
...@@ -1447,17 +1455,7 @@ fn transformExpression(...@@ -1447,17 +1455,7 @@ fn transformExpression(
1447 else1455 else
1448 .void;1456 .void;
14491457
1450 const coerced_operand = switch (break_result) {1458 const coerced_operand = try t.maybeCoerce(ctx.nodeOffset(node), operand, &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 };
14611459
1462 _ = try t.addIndex(.@"break", .{1460 _ = try t.addIndex(.@"break", .{
1463 .src_node = ctx.nodeOffset(node),1461 .src_node = ctx.nodeOffset(node),
...@@ -1697,6 +1695,52 @@ fn transformExpression(...@@ -1697,6 +1695,52 @@ fn transformExpression(
1697 });1695 });
1698 },1696 },
16991697
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 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),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,6 +2242,20 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData {
2198 return &t.insts.items(.data)[@intFromEnum(index)];2242 return &t.insts.items(.data)[@intFromEnum(index)];
2199}2243}
22002244
2245fn 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
2201fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList {2259fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList {
2202 const items = scratch_top.slice(u32);2260 const items = scratch_top.slice(u32);
2203 const extra_top = t.extra.list.items.len;2261 const extra_top = t.extra.list.items.len;
src/core/js/Link.zig+31-3
...@@ -388,6 +388,12 @@ fn printExprLevel(...@@ -388,6 +388,12 @@ fn printExprLevel(
388 }388 }
389 },389 },
390390
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 .stmt_declare_local => {397 .stmt_declare_local => {
392 const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node);398 const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node);
393 try writer.writeAll("let ");399 try writer.writeAll("let ");
...@@ -435,10 +441,14 @@ fn printExprLevel(...@@ -435,10 +441,14 @@ fn printExprLevel(
435 .eq,441 .eq,
436 .lt,442 .lt,
437 .gt,443 .gt,
444 .ge,
445 .le,
438 .assign_add,446 .assign_add,
439 .assign_sub,447 .assign_sub,
440 .assign_mul,448 .assign_mul,
441 .assign_div,449 .assign_div,
450 .@"and",
451 .@"or",
442 => |tag| {452 => |tag| {
443 const bin: Tree.Bin = tree.nodeData(tag, node);453 const bin: Tree.Bin = tree.nodeData(tag, node);
444 const op: Operator = @field(Operator, @tagName(tag));454 const op: Operator = @field(Operator, @tagName(tag));
...@@ -466,6 +476,14 @@ fn printExprLevel(...@@ -466,6 +476,14 @@ fn printExprLevel(
466 try writer.writeAll(")");476 try writer.writeAll(")");
467 },477 },
468478
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 inline .negate,487 inline .negate,
470 .bool_not,488 .bool_not,
471 .pre_dec,489 .pre_dec,
...@@ -483,7 +501,7 @@ fn printExprLevel(...@@ -483,7 +501,7 @@ fn printExprLevel(
483 const wrap = Level.conditional.needsParens(level);501 const wrap = Level.conditional.needsParens(level);
484 if (wrap)502 if (wrap)
485 try writer.writeAll("(");503 try writer.writeAll("(");
486 try link.printExpr(tree, ternary.cond, writer);504 try link.printExprLevel(tree, ternary.cond, .logical_or, writer);
487 try writer.writeAll(" ? ");505 try writer.writeAll(" ? ");
488 try link.printExpr(tree, data.yes, writer);506 try link.printExpr(tree, data.yes, writer);
489 try writer.writeAll(" : ");507 try writer.writeAll(" : ");
...@@ -788,10 +806,14 @@ const Operator = enum {...@@ -788,10 +806,14 @@ const Operator = enum {
788 eq,806 eq,
789 lt,807 lt,
790 gt,808 gt,
809 ge,
810 le,
791 negate,811 negate,
792 bool_not,812 bool_not,
793 pre_inc,813 pre_inc,
794 pre_dec,814 pre_dec,
815 @"and",
816 @"or",
795817
796 pub fn level(op: Operator) Level {818 pub fn level(op: Operator) Level {
797 return switch (op) {819 return switch (op) {
...@@ -804,8 +826,9 @@ const Operator = enum {...@@ -804,8 +826,9 @@ const Operator = enum {
804 .add, .sub => .additive,826 .add, .sub => .additive,
805 .mul, .div => .multiplicative,827 .mul, .div => .multiplicative,
806 .eq => .equality,828 .eq => .equality,
807 .lt, .gt => .relational,829 .lt, .gt, .ge, .le => .relational,
808830 .@"and" => .logical_and,
831 .@"or" => .logical_or,
809 .pre_inc,832 .pre_inc,
810 .pre_dec,833 .pre_dec,
811 .bool_not,834 .bool_not,
...@@ -828,6 +851,11 @@ const Operator = enum {...@@ -828,6 +851,11 @@ const Operator = enum {
828 .eq => "===",851 .eq => "===",
829 .lt => "<",852 .lt => "<",
830 .gt => ">",853 .gt => ">",
854 .le => "<=",
855 .ge => ">=",
856
857 .@"and" => "&&",
858 .@"or" => "||",
831859
832 .pre_inc => "++",860 .pre_inc => "++",
833 .pre_dec => "++",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,6 +157,8 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
157 inline .gt,157 inline .gt,
158 .lt,158 .lt,
159 .eq,159 .eq,
160 .ge,
161 .le,
160 .add,162 .add,
161 .sub,163 .sub,
162 .mul,164 .mul,
...@@ -250,6 +252,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -250,6 +252,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
250 try c.markExpression(inst, node);252 try c.markExpression(inst, node);
251 },253 },
252254
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 .field_opaque => {277 .field_opaque => {
254 const data: Air.Bin = c.air.instData(.field_opaque, inst);278 const data: Air.Bin = c.air.instData(.field_opaque, inst);
255 if (data.rhs.value()) |field_value| {279 if (data.rhs.value()) |field_value| {
...@@ -336,8 +360,34 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -336,8 +360,34 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
336 if (c.canLowerToExpression(extra.yes) and360 if (c.canLowerToExpression(extra.yes) and
337 c.canLowerToExpression(extra.no))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 const yes = try c.lowerToExpression(extra.yes);389 const yes = try c.lowerToExpression(extra.yes);
340 const no = try c.lowerToExpression(extra.yes);390 const no = try c.lowerToExpression(extra.no);
341391
342 const ternary = try c.add(.ternary, .{392 const ternary = try c.add(.ternary, .{
343 .cond = cond,393 .cond = cond,
...@@ -559,7 +609,7 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index {...@@ -559,7 +609,7 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index {
559 switch (flags.uses) {609 switch (flags.uses) {
560 .none => {610 .none => {
561 // incorrectly marked instruction as unused611 // 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 @intFromEnum(index),613 @intFromEnum(index),
564 });614 });
565 },615 },
...@@ -722,7 +772,6 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {...@@ -722,7 +772,6 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {
722 const next_label = c.next_label;772 const next_label = c.next_label;
723773
724 try c.scratch.appendSlice(c.arena, &.{774 try c.scratch.appendSlice(c.arena, &.{
725 // @intFromEnum(if (temporary) |temp| temp.toOptional() else .none),
726 @intFromEnum(Node.Index.Optional.none),775 @intFromEnum(Node.Index.Optional.none),
727 next_label,776 next_label,
728 });777 });
...@@ -749,6 +798,46 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {...@@ -749,6 +798,46 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {
749 }));798 }));
750}799}
751800
801fn 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// Ideally, we convert everything to an expression. This allows for much841// Ideally, we convert everything to an expression. This allows for much
753// more compact code. When that isnt possible (`return`, `while`, etc),842// more compact code. When that isnt possible (`return`, `while`, etc),
754// we detect that here and ensure to lower `if` to actual if statements,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,6 +863,8 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
774 .eq,863 .eq,
775 .lt,864 .lt,
776 .gt,865 .gt,
866 .ge,
867 .le,
777 .negate,868 .negate,
778 .bool_not,869 .bool_not,
779 .store,870 .store,
...@@ -787,15 +878,28 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {...@@ -787,15 +878,28 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
787 // Later, this could be made true if we can hoist temporaries up.878 // Later, this could be made true if we can hoist temporaries up.
788 .memory => false,879 .memory => false,
789880
790 // break/return must be a js statement881 // these must be lowered to js statements
791 .@"break",882 .@"break", .panic => false,
792 => false,
793883
884 // A block can become a comma expression if all expressions
885 // are lowerable. The break at the end just becomes the result.
794 .block => {886 .block => {
795 // TODO: blocks that are887 const block: Air.IndexList = c.air.instData(.block, index);
796 // - all statements except last are lowerable888 const items = block.slice(&c.air);
797 // - the last statement is lowerable OR a break of this block889 for (items[0 .. items.len - 1]) |item| {
798 return false;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 },
800904
801 .@"if" => {905 .@"if" => {
...@@ -818,6 +922,9 @@ fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index {...@@ -818,6 +922,9 @@ fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index {
818 return switch (ref.decode()) {922 return switch (ref.decode()) {
819 .value => |v| try c.valueToExpression(v),923 .value => |v| try c.valueToExpression(v),
820 .index => |index| {924 .index => |index| {
925 if (c.air.instTag(index) == .block) {
926 return try c.codeGenBlockAsExpression(index);
927 }
821 try c.codeGenInst(index);928 try c.codeGenInst(index);
822 return c.fetchExpression(ref);929 return c.fetchExpression(ref);
823 },930 },
src/core/js/Tree.zig+18
...@@ -39,6 +39,10 @@ pub const Tag = enum {...@@ -39,6 +39,10 @@ pub const Tag = enum {
39 eq,39 eq,
40 lt,40 lt,
41 gt,41 gt,
42 ge,
43 le,
44 @"and",
45 @"or",
42 // Exprs46 // Exprs
43 call,47 call,
44 js_new,48 js_new,
...@@ -47,6 +51,7 @@ pub const Tag = enum {...@@ -47,6 +51,7 @@ pub const Tag = enum {
47 object_literal,51 object_literal,
48 array,52 array,
49 index,53 index,
54 comma_chain,
50 // Prefix ops55 // Prefix ops
51 negate,56 negate,
52 bool_not,57 bool_not,
...@@ -63,6 +68,8 @@ pub const Tag = enum {...@@ -63,6 +68,8 @@ pub const Tag = enum {
63 stmt_return,68 stmt_return,
64 /// `...: { ... }`69 /// `...: { ... }`
65 stmt_block,70 stmt_block,
71 /// `throw x`
72 stmt_throw,
6673
67 pub fn Data(comptime tag: Tag) type {74 pub fn Data(comptime tag: Tag) type {
68 return switch (tag) {75 return switch (tag) {
...@@ -83,7 +90,11 @@ pub const Tag = enum {...@@ -83,7 +90,11 @@ pub const Tag = enum {
83 .eq,90 .eq,
84 .lt,91 .lt,
85 .gt,92 .gt,
93 .ge,
94 .le,
86 .index,95 .index,
96 .@"and",
97 .@"or",
87 => Bin,98 => Bin,
88 .ternary => Ternary,99 .ternary => Ternary,
89 .negate,100 .negate,
...@@ -95,9 +106,11 @@ pub const Tag = enum {...@@ -95,9 +106,11 @@ pub const Tag = enum {
95 .array => IndexList,106 .array => IndexList,
96 .call, .js_new => Call,107 .call, .js_new => Call,
97 .field => Field,108 .field => Field,
109 .comma_chain => IndexList,
98 .stmt_if => StmtIf,110 .stmt_if => StmtIf,
99 .stmt_break => StmtBreak,111 .stmt_break => StmtBreak,
100 .stmt_return => StmtReturn,112 .stmt_return => StmtReturn,
113 .stmt_throw => StmtThrow,
101 .stmt_declare_local => StmtDeclareLocal,114 .stmt_declare_local => StmtDeclareLocal,
102 .stmt_block => StmtBlock,115 .stmt_block => StmtBlock,
103 };116 };
...@@ -261,6 +274,11 @@ pub const StmtReturn = struct {...@@ -261,6 +274,11 @@ pub const StmtReturn = struct {
261 unused: Unused = .zero,274 unused: Unused = .zero,
262};275};
263276
277pub const StmtThrow = struct {
278 operand: Node.Index,
279 unused: Unused = .zero,
280};
281
264pub const StmtBlock = struct {282pub const StmtBlock = struct {
265 label: u32,283 label: u32,
266 content: ExtraIndex(IndexList),284 content: ExtraIndex(IndexList),
src/deps/quickjs/build.zig+2-2
...@@ -136,7 +136,7 @@ pub fn build(b: *Build) !void {...@@ -136,7 +136,7 @@ pub fn build(b: *Build) !void {
136 .optimize = optimize,136 .optimize = optimize,
137 .link_libc = true,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 const module = translate.addModule("quickjs");140 const module = translate.addModule("quickjs");
141 module.linkLibrary(libquickjs);141 module.linkLibrary(libquickjs);
142142
...@@ -147,7 +147,7 @@ pub fn build(b: *Build) !void {...@@ -147,7 +147,7 @@ pub fn build(b: *Build) !void {
147 .link_libc = true,147 .link_libc = true,
148 });148 });
149 const module2 = translate_libc.addModule("quickjs-libc");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 module2.linkLibrary(libquickjs);151 module2.linkLibrary(libquickjs);
152 module2.linkLibrary(libquickjs_libc);152 module2.linkLibrary(libquickjs_libc);
153}153}
test/behavior/array+2-2
...@@ -1,8 +1,7 @@...@@ -1,8 +1,7 @@
1let assertEqual = @extern("assertEqual");1let assertEqual = @extern("assertEqual");
22
3fn main {3fn main {
4 let array = [i(9), i(3), i(8), i(3), i(-1)];4 let array = a([9, 3, 8, 3, -1]);
5
6 assertEqual(array.len, i(5));5 assertEqual(array.len, i(5));
7 assertEqual(array[0], i(9));6 assertEqual(array[0], i(9));
8 assertEqual(array[1], i(3));7 assertEqual(array[1], i(3));
...@@ -12,3 +11,4 @@ fn main {...@@ -12,3 +11,4 @@ fn main {
12}11}
1312
14fn i(a: int) { a }13fn i(a: int) { a }
14fn 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,7 +506,7 @@ pub fn panic(msg: []const u8, trace: ?*const std.builtin.StackTrace, first_trace
506 if (current_case) |case| {506 if (current_case) |case| {
507 std.debug.print("Crash during case: {s}\n", .{case.display});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}
511511
512const std = @import("std");512const std = @import("std");