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 {
352352 else_body_len: u32,
353353
354354 /// Trailing:
355 /// * item: Inst.Ref // for each `items_len`.
355356 /// * instruction index for each `body_len`.
356357 pub const Case = struct {
357 item: Inst.Ref,
358 items_len: u32,
358359 body_len: u32,
359360 };
360361};
src/Module.zig+5-1
......@@ -1300,6 +1300,10 @@ pub const Scope = struct {
13001300 }
13011301
13021302 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 {
13031307 const sema = block.sema;
13041308 const gpa = sema.gpa;
13051309
......@@ -1309,7 +1313,7 @@ pub const Scope = struct {
13091313 const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
13101314 sema.air_instructions.appendAssumeCapacity(inst);
13111315 block.instructions.appendAssumeCapacity(result_index);
1312 return Air.indexToRef(result_index);
1316 return result_index;
13131317 }
13141318 };
13151319};
src/Sema.zig+190-148
......@@ -4170,159 +4170,201 @@ fn analyzeSwitch(
41704170
41714171 try sema.requireRuntimeBlock(block, src);
41724172
4173 // TODO when reworking AIR memory layout make multi cases get generated as cases,
4174 // not as part of the "else" block.
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;
4173 var cases_extra: std.ArrayListUnmanaged(u32) = .{};
4174 defer cases_extra.deinit(gpa);
41944175
4195 // case_block.instructions.shrinkRetainingCapacity(0);
4196 // const item = sema.resolveInst(item_ref);
4197 // // We validate these above; these two calls are guaranteed to succeed.
4198 // const item_val = sema.resolveConstValue(&case_block, .unneeded, item) catch unreachable;
4176 try cases_extra.ensureTotalCapacity(gpa, (scalar_cases_len + multi_cases_len) *
4177 @typeInfo(Air.SwitchBr.Case).Struct.fields.len + 2);
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] = .{
4203 // .item = item_val,
4204 // .body = .{ .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items) },
4205 // };
4206 //}
4185 var extra_index: usize = special.end;
42074186
4208 //var first_else_body: Body = undefined;
4209 //var prev_condbr: ?*Inst.CondBr = null;
4187 var scalar_i: usize = 0;
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;
4212 //while (multi_i < multi_cases_len) : (multi_i += 1) {
4213 // const items_len = sema.code.extra[extra_index];
4214 // extra_index += 1;
4215 // const ranges_len = sema.code.extra[extra_index];
4216 // extra_index += 1;
4217 // const body_len = sema.code.extra[extra_index];
4218 // extra_index += 1;
4219 // const items = sema.code.refSlice(extra_index, items_len);
4220 // extra_index += items_len;
4221
4222 // case_block.instructions.shrinkRetainingCapacity(0);
4223
4224 // var any_ok: ?Air.Inst.Index = null;
4225
4226 // for (items) |item_ref| {
4227 // const item = sema.resolveInst(item_ref);
4228 // _ = try sema.resolveConstValue(&child_block, item.src, item);
4229
4230 // const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item);
4231 // if (any_ok) |some| {
4232 // any_ok = try case_block.addBinOp(.bool_or, some, cmp_ok);
4233 // } else {
4234 // any_ok = cmp_ok;
4235 // }
4236 // }
4237
4238 // var range_i: usize = 0;
4239 // while (range_i < ranges_len) : (range_i += 1) {
4240 // const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4241 // extra_index += 1;
4242 // const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4243 // extra_index += 1;
4244
4245 // const item_first = sema.resolveInst(first_ref);
4246 // const item_last = sema.resolveInst(last_ref);
4247
4248 // _ = try sema.resolveConstValue(&child_block, item_first.src, item_first);
4249 // _ = try sema.resolveConstValue(&child_block, item_last.src, item_last);
4250
4251 // // operand >= first and operand <= last
4252 // const range_first_ok = try case_block.addBinOp(
4253 // .cmp_gte,
4254 // operand,
4255 // item_first,
4256 // );
4257 // const range_last_ok = try case_block.addBinOp(
4258 // .cmp_lte,
4259 // operand,
4260 // item_last,
4261 // );
4262 // const range_ok = try case_block.addBinOp(
4263 // .bool_and,
4264 // range_first_ok,
4265 // range_last_ok,
4266 // );
4267 // if (any_ok) |some| {
4268 // any_ok = try case_block.addBinOp(.bool_or, some, range_ok);
4269 // } else {
4270 // any_ok = range_ok;
4271 // }
4272 // }
4273
4274 // const new_condbr = try sema.arena.create(Inst.CondBr);
4275 // new_condbr.* = .{
4276 // .base = .{
4277 // .tag = .condbr,
4278 // .ty = Type.initTag(.noreturn),
4279 // .src = src,
4280 // },
4281 // .condition = any_ok.?,
4282 // .then_body = undefined,
4283 // .else_body = undefined,
4284 // };
4285 // try case_block.instructions.append(gpa, &new_condbr.base);
4286
4287 // const cond_body: Body = .{
4288 // .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),
4289 // };
4290
4291 // case_block.instructions.shrinkRetainingCapacity(0);
4292 // const body = sema.code.extra[extra_index..][0..body_len];
4293 // extra_index += body_len;
4294 // _ = try sema.analyzeBody(&case_block, body);
4295 // new_condbr.then_body = .{
4296 // .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),
4297 // };
4298 // if (prev_condbr) |condbr| {
4299 // condbr.else_body = cond_body;
4300 // } else {
4301 // first_else_body = cond_body;
4302 // }
4303 // prev_condbr = new_condbr;
4304 //}
4305
4306 //const final_else_body: Body = blk: {
4307 // if (special.body.len != 0) {
4308 // case_block.instructions.shrinkRetainingCapacity(0);
4309 // _ = try sema.analyzeBody(&case_block, special.body);
4310 // const else_body: Body = .{
4311 // .instructions = try sema.arena.dupe(Air.Inst.Index, case_block.instructions.items),
4312 // };
4313 // if (prev_condbr) |condbr| {
4314 // condbr.else_body = else_body;
4315 // break :blk first_else_body;
4316 // } else {
4317 // break :blk else_body;
4318 // }
4319 // } else {
4320 // break :blk .{ .instructions = &.{} };
4321 // }
4322 //};
4323
4324 //_ = try child_block.addSwitchBr(src, operand, cases, final_else_body);
4325 //return sema.analyzeBlockBody(block, src, &child_block, merges);
4196 case_block.instructions.shrinkRetainingCapacity(0);
4197 const item = sema.resolveInst(item_ref);
4198 // `item` is already guaranteed to be constant known.
4199
4200 _ = try sema.analyzeBody(&case_block, body);
4201
4202 try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len);
4203 cases_extra.appendAssumeCapacity(1); // items_len
4204 cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
4205 cases_extra.appendAssumeCapacity(@enumToInt(item));
4206 cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
4207 }
4208
4209 var is_first = true;
4210 var prev_cond_br: Air.Inst.Index = undefined;
4211 var first_else_body: []const Air.Inst.Index = &.{};
4212 defer gpa.free(first_else_body);
4213 var prev_then_body: []const Air.Inst.Index = &.{};
4214 defer gpa.free(prev_then_body);
4215
4216 var multi_i: usize = 0;
4217 while (multi_i < multi_cases_len) : (multi_i += 1) {
4218 const items_len = sema.code.extra[extra_index];
4219 extra_index += 1;
4220 const ranges_len = sema.code.extra[extra_index];
4221 extra_index += 1;
4222 const body_len = sema.code.extra[extra_index];
4223 extra_index += 1;
4224 const items = sema.code.refSlice(extra_index, items_len);
4225 extra_index += items_len;
4226
4227 case_block.instructions.shrinkRetainingCapacity(0);
4228
4229 var any_ok: Air.Inst.Ref = .none;
4230
4231 // If there are any ranges, we have to put all the items into the
4232 // else prong. Otherwise, we can take advantage of multiple items
4233 // mapping to the same body.
4234 if (ranges_len == 0) {
4235 const body = sema.code.extra[extra_index..][0..body_len];
4236 extra_index += body_len;
4237 _ = try sema.analyzeBody(&case_block, body);
4238
4239 try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len +
4240 case_block.instructions.items.len);
4241
4242 cases_extra.appendAssumeCapacity(1); // items_len
4243 cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len));
4244
4245 for (items) |item_ref| {
4246 const item = sema.resolveInst(item_ref);
4247 cases_extra.appendAssumeCapacity(@enumToInt(item));
4248 }
4249
4250 cases_extra.appendSliceAssumeCapacity(case_block.instructions.items);
4251 } else {
4252 for (items) |item_ref| {
4253 const item = sema.resolveInst(item_ref);
4254 const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item);
4255 if (any_ok != .none) {
4256 any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok);
4257 } else {
4258 any_ok = cmp_ok;
4259 }
4260 }
4261
4262 var range_i: usize = 0;
4263 while (range_i < ranges_len) : (range_i += 1) {
4264 const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4265 extra_index += 1;
4266 const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
4267 extra_index += 1;
4268
4269 const item_first = sema.resolveInst(first_ref);
4270 const item_last = sema.resolveInst(last_ref);
4271
4272 // operand >= first and operand <= last
4273 const range_first_ok = try case_block.addBinOp(
4274 .cmp_gte,
4275 operand,
4276 item_first,
4277 );
4278 const range_last_ok = try case_block.addBinOp(
4279 .cmp_lte,
4280 operand,
4281 item_last,
4282 );
4283 const range_ok = try case_block.addBinOp(
4284 .bool_and,
4285 range_first_ok,
4286 range_last_ok,
4287 );
4288 if (any_ok != .none) {
4289 any_ok = try case_block.addBinOp(.bool_or, any_ok, range_ok);
4290 } else {
4291 any_ok = range_ok;
4292 }
4293 }
4294
4295 const new_cond_br = try case_block.addInstAsIndex(.{ .tag = .cond_br, .data = .{
4296 .pl_op = .{
4297 .operand = any_ok,
4298 .payload = undefined,
4299 },
4300 } });
4301 var cond_body = case_block.instructions.toOwnedSlice(gpa);
4302 defer gpa.free(cond_body);
4303
4304 case_block.instructions.shrinkRetainingCapacity(0);
4305 const body = sema.code.extra[extra_index..][0..body_len];
4306 extra_index += body_len;
4307 _ = try sema.analyzeBody(&case_block, body);
4308
4309 if (is_first) {
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);
43264368}
43274369
43284370fn resolveSwitchItemVal(
src/codegen/wasm.zig+43-38
......@@ -1282,44 +1282,49 @@ pub const Context = struct {
12821282 // result type is always 'noreturn'
12831283 const blocktype = wasm.block_empty;
12841284
1285 const signedness: std.builtin.Signedness = blk: {
1286 // by default we tell the operand type is unsigned (i.e. bools and enum values)
1287 if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1288
1289 // incase of an actual integer, we emit the correct signedness
1290 break :blk target_ty.intInfo(self.target).signedness;
1291 };
1292 for (cases) |case_idx| {
1293 const case = self.air.extraData(Air.SwitchBr.Case, case_idx);
1294 const case_body = self.air.extra[case.end..][0..case.data.body_len];
1295
1296 // create a block for each case, when the condition does not match we break out of it
1297 try self.startBlock(.block, blocktype, null);
1298 try self.emitWValue(target);
1299
1300 const val = self.air.value(case.data.item).?;
1301 try self.emitConstant(val, target_ty);
1302 const opcode = buildOpcode(.{
1303 .valtype1 = valtype,
1304 .op = .ne, // not equal because we jump out the block if it does not match the condition
1305 .signedness = signedness,
1306 });
1307 try self.code.append(wasm.opcode(opcode));
1308 try self.code.append(wasm.opcode(.br_if));
1309 try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1310
1311 // emit our block code
1312 try self.genBody(case_body);
1313
1314 // end the block we created earlier
1315 try self.endBlock();
1316 }
1317
1318 // finally, emit the else case if it exists. Here we will not have to
1319 // check for a condition, so also no need to emit a block.
1320 try self.genBody(else_body);
1321
1322 return .none;
1285 _ = valtype;
1286 _ = blocktype;
1287 _ = target;
1288 _ = else_body;
1289 return self.fail("TODO implement wasm codegen for switch", .{});
1290 //const signedness: std.builtin.Signedness = blk: {
1291 // // by default we tell the operand type is unsigned (i.e. bools and enum values)
1292 // if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
1293
1294 // // incase of an actual integer, we emit the correct signedness
1295 // break :blk target_ty.intInfo(self.target).signedness;
1296 //};
1297 //for (cases) |case_idx| {
1298 // const case = self.air.extraData(Air.SwitchBr.Case, case_idx);
1299 // const case_body = self.air.extra[case.end..][0..case.data.body_len];
1300
1301 // // create a block for each case, when the condition does not match we break out of it
1302 // try self.startBlock(.block, blocktype, null);
1303 // try self.emitWValue(target);
1304
1305 // const val = self.air.value(case.data.item).?;
1306 // try self.emitConstant(val, target_ty);
1307 // const opcode = buildOpcode(.{
1308 // .valtype1 = valtype,
1309 // .op = .ne, // not equal because we jump out the block if it does not match the condition
1310 // .signedness = signedness,
1311 // });
1312 // try self.code.append(wasm.opcode(opcode));
1313 // try self.code.append(wasm.opcode(.br_if));
1314 // try leb.writeULEB128(self.code.writer(), @as(u32, 0));
1315
1316 // // emit our block code
1317 // try self.genBody(case_body);
1318
1319 // // end the block we created earlier
1320 // try self.endBlock();
1321 //}
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;
13231328 }
13241329
13251330 fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {