authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-19 17:35:14-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-20 12:19:17-07:00
logea902ffe8f5f337b04f25b4efc69599db74d99ce
tree7c0f184759b9ca8ebf9db49aa04ac91042eff705
parentcaa0de545e2f45a96ac3136178f478dab1c89ebd

Sema: reimplement runtime switch

Now supports multiple items pointing to the same body. This is a common pattern even when using a jump table, with multiple cases pointing to the same block of code. In the case of a range specified, the items are moved to branches in the else body. A future improvement may make it possible to have jump table items as well as ranges pointing to the same block of code.

4 files changed, 240 insertions(+), 188 deletions(-)

src/Air.zig+2-1
...@@ -352,9 +352,10 @@ pub const SwitchBr = struct {...@@ -352,9 +352,10 @@ pub const SwitchBr = struct {
352 else_body_len: u32,352 else_body_len: u32,
353353
354 /// Trailing:354 /// Trailing:
355 /// * item: Inst.Ref // for each `items_len`.
355 /// * instruction index for each `body_len`.356 /// * instruction index for each `body_len`.
356 pub const Case = struct {357 pub const Case = struct {
357 item: Inst.Ref,358 items_len: u32,
358 body_len: u32,359 body_len: u32,
359 };360 };
360};361};
src/Module.zig+5-1
...@@ -1300,6 +1300,10 @@ pub const Scope = struct {...@@ -1300,6 +1300,10 @@ pub const Scope = struct {
1300 }1300 }
13011301
1302 pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {1302 pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
1303 return Air.indexToRef(try block.addInstAsIndex(inst));
1304 }
1305
1306 pub fn addInstAsIndex(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
1303 const sema = block.sema;1307 const sema = block.sema;
1304 const gpa = sema.gpa;1308 const gpa = sema.gpa;
13051309
...@@ -1309,7 +1313,7 @@ pub const Scope = struct {...@@ -1309,7 +1313,7 @@ pub const Scope = struct {
1309 const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);1313 const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
1310 sema.air_instructions.appendAssumeCapacity(inst);1314 sema.air_instructions.appendAssumeCapacity(inst);
1311 block.instructions.appendAssumeCapacity(result_index);1315 block.instructions.appendAssumeCapacity(result_index);
1312 return Air.indexToRef(result_index);1316 return result_index;
1313 }1317 }
1314 };1318 };
1315};1319};
src/Sema.zig+190-148
...@@ -4170,159 +4170,201 @@ fn analyzeSwitch(...@@ -4170,159 +4170,201 @@ fn analyzeSwitch(
41704170
4171 try sema.requireRuntimeBlock(block, src);4171 try sema.requireRuntimeBlock(block, src);
41724172
4173 // TODO when reworking AIR memory layout make multi cases get generated as cases,4173 var cases_extra: std.ArrayListUnmanaged(u32) = .{};
4174 // not as part of the "else" block.4174 defer cases_extra.deinit(gpa);
4175 return mod.fail(&block.base, src, "TODO rework runtime switch Sema", .{});
4176 //const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
4177
4178 //var case_block = child_block.makeSubBlock();
4179 //case_block.runtime_loop = null;
4180 //case_block.runtime_cond = operand.src;
4181 //case_block.runtime_index += 1;
4182 //defer case_block.instructions.deinit(gpa);
4183
4184 //var extra_index: usize = special.end;
4185
4186 //var scalar_i: usize = 0;
4187 //while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
4188 // const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4189 // extra_index += 1;
4190 // const body_len = sema.code.extra[extra_index];
4191 // extra_index += 1;
4192 // const body = sema.code.extra[extra_index..][0..body_len];
4193 // extra_index += body_len;
41944175
4195 // case_block.instructions.shrinkRetainingCapacity(0);4176 try cases_extra.ensureTotalCapacity(gpa, (scalar_cases_len + multi_cases_len) *
4196 // const item = sema.resolveInst(item_ref);4177 @typeInfo(Air.SwitchBr.Case).Struct.fields.len + 2);
4197 // // We validate these above; these two calls are guaranteed to succeed.
4198 // const item_val = sema.resolveConstValue(&case_block, .unneeded, item) catch unreachable;
41994178
4200 // _ = try sema.analyzeBody(&case_block, body);4179 var case_block = child_block.makeSubBlock();
4180 case_block.runtime_loop = null;
4181 case_block.runtime_cond = operand_src;
4182 case_block.runtime_index += 1;
4183 defer case_block.instructions.deinit(gpa);
42014184
4202 // cases[scalar_i] = .{4185 var extra_index: usize = special.end;
4203 // .item = item_val,
4204 // .body = .{ .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items) },
4205 // };
4206 //}
42074186
4208 //var first_else_body: Body = undefined;4187 var scalar_i: usize = 0;
4209 //var prev_condbr: ?*Inst.CondBr = null;4188 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
4189 const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4190 extra_index += 1;
4191 const body_len = sema.code.extra[extra_index];
4192 extra_index += 1;
4193 const body = sema.code.extra[extra_index..][0..body_len];
4194 extra_index += body_len;
42104195
4211 //var multi_i: usize = 0;4196 case_block.instructions.shrinkRetainingCapacity(0);
4212 //while (multi_i < multi_cases_len) : (multi_i += 1) {4197 const item = sema.resolveInst(item_ref);
4213 // const items_len = sema.code.extra[extra_index];4198 // `item` is already guaranteed to be constant known.
4214 // extra_index += 1;4199
4215 // const ranges_len = sema.code.extra[extra_index];4200 _ = try sema.analyzeBody(&case_block, body);
4216 // extra_index += 1;4201
4217 // const body_len = sema.code.extra[extra_index];4202 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
4218 // extra_index += 1;4203 cases_extra.appendAssumeCapacity(1); // items_len
4219 // const items = sema.code.refSlice(extra_index, items_len);4204 cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
4220 // extra_index += items_len;4205 cases_extra.appendAssumeCapacity(@enumToInt(item));
42214206 cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
4222 // case_block.instructions.shrinkRetainingCapacity(0);4207 }
42234208
4224 // var any_ok: ?Air.Inst.Index = null;4209 var is_first = true;
42254210 var prev_cond_br: Air.Inst.Index = undefined;
4226 // for (items) |item_ref| {4211 var first_else_body: []const Air.Inst.Index = &.{};
4227 // const item = sema.resolveInst(item_ref);4212 defer gpa.free(first_else_body);
4228 // _ = try sema.resolveConstValue(&child_block, item.src, item);4213 var prev_then_body: []const Air.Inst.Index = &.{};
42294214 defer gpa.free(prev_then_body);
4230 // const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item);4215
4231 // if (any_ok) |some| {4216 var multi_i: usize = 0;
4232 // any_ok = try case_block.addBinOp(.bool_or, some, cmp_ok);4217 while (multi_i < multi_cases_len) : (multi_i += 1) {
4233 // } else {4218 const items_len = sema.code.extra[extra_index];
4234 // any_ok = cmp_ok;4219 extra_index += 1;
4235 // }4220 const ranges_len = sema.code.extra[extra_index];
4236 // }4221 extra_index += 1;
42374222 const body_len = sema.code.extra[extra_index];
4238 // var range_i: usize = 0;4223 extra_index += 1;
4239 // while (range_i < ranges_len) : (range_i += 1) {4224 const items = sema.code.refSlice(extra_index, items_len);
4240 // const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);4225 extra_index += items_len;
4241 // extra_index += 1;4226
4242 // const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);4227 case_block.instructions.shrinkRetainingCapacity(0);
4243 // extra_index += 1;4228
42444229 var any_ok: Air.Inst.Ref = .none;
4245 // const item_first = sema.resolveInst(first_ref);4230
4246 // const item_last = sema.resolveInst(last_ref);4231 // If there are any ranges, we have to put all the items into the
42474232 // else prong. Otherwise, we can take advantage of multiple items
4248 // _ = try sema.resolveConstValue(&child_block, item_first.src, item_first);4233 // mapping to the same body.
4249 // _ = try sema.resolveConstValue(&child_block, item_last.src, item_last);4234 if (ranges_len == 0) {
42504235 const body = sema.code.extra[extra_index..][0..body_len];
4251 // // operand >= first and operand <= last4236 extra_index += body_len;
4252 // const range_first_ok = try case_block.addBinOp(4237 _ = try sema.analyzeBody(&case_block, body);
4253 // .cmp_gte,4238
4254 // operand,4239 try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len +
4255 // item_first,4240 case_block.instructions.items.len);
4256 // );4241
4257 // const range_last_ok = try case_block.addBinOp(4242 cases_extra.appendAssumeCapacity(1); // items_len
4258 // .cmp_lte,4243 cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
4259 // operand,4244
4260 // item_last,4245 for (items) |item_ref| {
4261 // );4246 const item = sema.resolveInst(item_ref);
4262 // const range_ok = try case_block.addBinOp(4247 cases_extra.appendAssumeCapacity(@enumToInt(item));
4263 // .bool_and,4248 }
4264 // range_first_ok,4249
4265 // range_last_ok,4250 cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
4266 // );4251 } else {
4267 // if (any_ok) |some| {4252 for (items) |item_ref| {
4268 // any_ok = try case_block.addBinOp(.bool_or, some, range_ok);4253 const item = sema.resolveInst(item_ref);
4269 // } else {4254 const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item);
4270 // any_ok = range_ok;4255 if (any_ok != .none) {
4271 // }4256 any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok);
4272 // }4257 } else {
42734258 any_ok = cmp_ok;
4274 // const new_condbr = try sema.arena.create(Inst.CondBr);4259 }
4275 // new_condbr.* = .{4260 }
4276 // .base = .{4261
4277 // .tag = .condbr,4262 var range_i: usize = 0;
4278 // .ty = Type.initTag(.noreturn),4263 while (range_i < ranges_len) : (range_i += 1) {
4279 // .src = src,4264 const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4280 // },4265 extra_index += 1;
4281 // .condition = any_ok.?,4266 const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4282 // .then_body = undefined,4267 extra_index += 1;
4283 // .else_body = undefined,4268
4284 // };4269 const item_first = sema.resolveInst(first_ref);
4285 // try case_block.instructions.append(gpa, &new_condbr.base);4270 const item_last = sema.resolveInst(last_ref);
42864271
4287 // const cond_body: Body = .{4272 // operand >= first and operand <= last
4288 // .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),4273 const range_first_ok = try case_block.addBinOp(
4289 // };4274 .cmp_gte,
42904275 operand,
4291 // case_block.instructions.shrinkRetainingCapacity(0);4276 item_first,
4292 // const body = sema.code.extra[extra_index..][0..body_len];4277 );
4293 // extra_index += body_len;4278 const range_last_ok = try case_block.addBinOp(
4294 // _ = try sema.analyzeBody(&case_block, body);4279 .cmp_lte,
4295 // new_condbr.then_body = .{4280 operand,
4296 // .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),4281 item_last,
4297 // };4282 );
4298 // if (prev_condbr) |condbr| {4283 const range_ok = try case_block.addBinOp(
4299 // condbr.else_body = cond_body;4284 .bool_and,
4300 // } else {4285 range_first_ok,
4301 // first_else_body = cond_body;4286 range_last_ok,
4302 // }4287 );
4303 // prev_condbr = new_condbr;4288 if (any_ok != .none) {
4304 //}4289 any_ok = try case_block.addBinOp(.bool_or, any_ok, range_ok);
43054290 } else {
4306 //const final_else_body: Body = blk: {4291 any_ok = range_ok;
4307 // if (special.body.len != 0) {4292 }
4308 // case_block.instructions.shrinkRetainingCapacity(0);4293 }
4309 // _ = try sema.analyzeBody(&case_block, special.body);4294
4310 // const else_body: Body = .{4295 const new_cond_br = try case_block.addInstAsIndex(.{ .tag = .cond_br, .data = .{
4311 // .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),4296 .pl_op = .{
4312 // };4297 .operand = any_ok,
4313 // if (prev_condbr) |condbr| {4298 .payload = undefined,
4314 // condbr.else_body = else_body;4299 },
4315 // break :blk first_else_body;4300 } });
4316 // } else {4301 var cond_body = case_block.instructions.toOwnedSlice(gpa);
4317 // break :blk else_body;4302 defer gpa.free(cond_body);
4318 // }4303
4319 // } else {4304 case_block.instructions.shrinkRetainingCapacity(0);
4320 // break :blk .{ .instructions = &.{} };4305 const body = sema.code.extra[extra_index..][0..body_len];
4321 // }4306 extra_index += body_len;
4322 //};4307 _ = try sema.analyzeBody(&case_block, body);
43234308
4324 //_ = try child_block.addSwitchBr(src, operand, cases, final_else_body);4309 if (is_first) {
4325 //return sema.analyzeBlockBody(block, src, &child_block, merges);4310 is_first = false;
4311 first_else_body = cond_body;
4312 cond_body = &.{};
4313 } else {
4314 try sema.air_extra.ensureUnusedCapacity(
4315 gpa,
4316 @typeInfo(Air.CondBr).Struct.fields.len + prev_then_body.len + cond_body.len,
4317 );
4318
4319 sema.air_instructions.items(.data)[prev_cond_br].pl_op.payload =
4320 sema.addExtraAssumeCapacity(Air.CondBr{
4321 .then_body_len = @intCast(u32, prev_then_body.len),
4322 .else_body_len = @intCast(u32, cond_body.len),
4323 });
4324 sema.air_extra.appendSliceAssumeCapacity(prev_then_body);
4325 sema.air_extra.appendSliceAssumeCapacity(cond_body);
4326 }
4327 prev_then_body = case_block.instructions.toOwnedSlice(gpa);
4328 prev_cond_br = new_cond_br;
4329 }
4330 }
4331
4332 var final_else_body: []const Air.Inst.Index = &.{};
4333 if (special.body.len != 0) {
4334 case_block.instructions.shrinkRetainingCapacity(0);
4335 _ = try sema.analyzeBody(&case_block, special.body);
4336
4337 if (is_first) {
4338 final_else_body = case_block.instructions.items;
4339 } else {
4340 try sema.air_extra.ensureUnusedCapacity(gpa, prev_then_body.len +
4341 @typeInfo(Air.CondBr).Struct.fields.len + case_block.instructions.items.len);
4342
4343 sema.air_instructions.items(.data)[prev_cond_br].pl_op.payload =
4344 sema.addExtraAssumeCapacity(Air.CondBr{
4345 .then_body_len = @intCast(u32, prev_then_body.len),
4346 .else_body_len = @intCast(u32, case_block.instructions.items.len),
4347 });
4348 sema.air_extra.appendSliceAssumeCapacity(prev_then_body);
4349 sema.air_extra.appendSliceAssumeCapacity(case_block.instructions.items);
4350 final_else_body = first_else_body;
4351 }
4352 }
4353
4354 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).Struct.fields.len +
4355 cases_extra.items.len);
4356
4357 _ = try child_block.addInst(.{ .tag = .switch_br, .data = .{ .pl_op = .{
4358 .operand = operand,
4359 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
4360 .cases_len = @intCast(u32, scalar_cases_len + multi_cases_len),
4361 .else_body_len = @intCast(u32, final_else_body.len),
4362 }),
4363 } } });
4364 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
4365 sema.air_extra.appendSliceAssumeCapacity(final_else_body);
4366
4367 return sema.analyzeBlockBody(block, src, &child_block, merges);
4326}4368}
43274369
4328fn resolveSwitchItemVal(4370fn resolveSwitchItemVal(
src/codegen/wasm.zig+43-38
...@@ -1282,44 +1282,49 @@ pub const Context = struct {...@@ -1282,44 +1282,49 @@ pub const Context = struct {
1282 // result type is always 'noreturn'1282 // result type is always 'noreturn'
1283 const blocktype = wasm.block_empty;1283 const blocktype = wasm.block_empty;
12841284
1285 const signedness: std.builtin.Signedness = blk: {1285 _ = valtype;
1286 // by default we tell the operand type is unsigned (i.e. bools and enum values)1286 _ = blocktype;
1287 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;1287 _ = target;
12881288 _ = else_body;
1289 // incase of an actual integer, we emit the correct signedness1289 return self.fail("TODO implement wasm codegen for switch", .{});
1290 break :blk target_ty.intInfo(self.target).signedness;1290 //const signedness: std.builtin.Signedness = blk: {
1291 };1291 // // by default we tell the operand type is unsigned (i.e. bools and enum values)
1292 for (cases) |case_idx| {1292 // if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1293 const case = self.air.extraData(Air.SwitchBr.Case, case_idx);1293
1294 const case_body = self.air.extra[case.end..][0..case.data.body_len];1294 // // incase of an actual integer, we emit the correct signedness
12951295 // break :blk target_ty.intInfo(self.target).signedness;
1296 // create a block for each case, when the condition does not match we break out of it1296 //};
1297 try self.startBlock(.block, blocktype, null);1297 //for (cases) |case_idx| {
1298 try self.emitWValue(target);1298 // const case = self.air.extraData(Air.SwitchBr.Case, case_idx);
12991299 // const case_body = self.air.extra[case.end..][0..case.data.body_len];
1300 const val = self.air.value(case.data.item).?;1300
1301 try self.emitConstant(val, target_ty);1301 // // create a block for each case, when the condition does not match we break out of it
1302 const opcode = buildOpcode(.{1302 // try self.startBlock(.block, blocktype, null);
1303 .valtype1 = valtype,1303 // try self.emitWValue(target);
1304 .op = .ne, // not equal because we jump out the block if it does not match the condition1304
1305 .signedness = signedness,1305 // const val = self.air.value(case.data.item).?;
1306 });1306 // try self.emitConstant(val, target_ty);
1307 try self.code.append(wasm.opcode(opcode));1307 // const opcode = buildOpcode(.{
1308 try self.code.append(wasm.opcode(.br_if));1308 // .valtype1 = valtype,
1309 try leb.writeULEB128(self.code.writer(), @as(u32, 0));1309 // .op = .ne, // not equal because we jump out the block if it does not match the condition
13101310 // .signedness = signedness,
1311 // emit our block code1311 // });
1312 try self.genBody(case_body);1312 // try self.code.append(wasm.opcode(opcode));
13131313 // try self.code.append(wasm.opcode(.br_if));
1314 // end the block we created earlier1314 // try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1315 try self.endBlock();1315
1316 }1316 // // emit our block code
13171317 // try self.genBody(case_body);
1318 // finally, emit the else case if it exists. Here we will not have to1318
1319 // check for a condition, so also no need to emit a block.1319 // // end the block we created earlier
1320 try self.genBody(else_body);1320 // try self.endBlock();
13211321 //}
1322 return .none;1322
1323 //// finally, emit the else case if it exists. Here we will not have to
1324 //// check for a condition, so also no need to emit a block.
1325 //try self.genBody(else_body);
1326
1327 //return .none;
1323 }1328 }
13241329
1325 fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {1330 fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {