Adds the `inline_asm: InlineAsm` opcode to the IR Op union (inst.zig): interned template + operand list (role/name/constraint/operand) + interned clobber names + has_side_effects; the result rides on Inst.ty (void / scalar / tuple). The new variant forces coverage in the exhaustive Op switches: - interp.zig: loud bailDetail — inline asm is never comptime-evaluable. - print.zig: an IR-dump arm. - emit_llvm.zig: a @panic TRIPWIRE — emit lands in Phase D, and until then lowerAsmExpr still bails, so no inline_asm op is ever created. Reaching emit would mean lowering switched over before emit was ready; crash loudly rather than miscompile. No behavior change: lowering still bails, the op is constructed only in the new `inline_asm op shape` unit test (inst.test.zig). zig build test green (652 corpus, 446 unit).
99 lines
3.4 KiB
Zig
99 lines
3.4 KiB
Zig
// Tests for inst.zig
|
|
|
|
const std = @import("std");
|
|
const types = @import("types.zig");
|
|
const inst_mod = @import("inst.zig");
|
|
|
|
const Ref = inst_mod.Ref;
|
|
const BlockId = inst_mod.BlockId;
|
|
const FuncId = inst_mod.FuncId;
|
|
const Inst = inst_mod.Inst;
|
|
const Block = inst_mod.Block;
|
|
const Function = inst_mod.Function;
|
|
const InlineAsm = inst_mod.InlineAsm;
|
|
const StringId = types.StringId;
|
|
|
|
test "Ref none sentinel" {
|
|
try std.testing.expect(Ref.none.isNone());
|
|
try std.testing.expect(!Ref.fromIndex(0).isNone());
|
|
}
|
|
|
|
test "basic instruction creation" {
|
|
const inst = Inst{
|
|
.op = .{ .add = .{ .lhs = Ref.fromIndex(0), .rhs = Ref.fromIndex(1) } },
|
|
.ty = .i32,
|
|
};
|
|
try std.testing.expectEqual(types.TypeId.i32, inst.ty);
|
|
switch (inst.op) {
|
|
.add => |bin| {
|
|
try std.testing.expectEqual(Ref.fromIndex(0), bin.lhs);
|
|
try std.testing.expectEqual(Ref.fromIndex(1), bin.rhs);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
test "block creation" {
|
|
const alloc = std.testing.allocator;
|
|
var block = Block.init(@enumFromInt(1), &.{});
|
|
defer block.deinit(alloc);
|
|
|
|
block.insts.append(alloc, .{
|
|
.op = .{ .const_int = 42 },
|
|
.ty = .i64,
|
|
}) catch unreachable;
|
|
block.insts.append(alloc, .{
|
|
.op = .{ .ret = .{ .operand = Ref.fromIndex(0) } },
|
|
.ty = .i64,
|
|
}) catch unreachable;
|
|
|
|
try std.testing.expectEqual(@as(usize, 2), block.insts.items.len);
|
|
}
|
|
|
|
test "inline_asm op shape (ASM stream Phase C.0)" {
|
|
// out_value (yields the value, operand = .none) + a named-less input,
|
|
// plus two clobbers; result rides on Inst.ty.
|
|
const operands = [_]InlineAsm.AsmOperand{
|
|
.{ .role = .out_value, .name = @enumFromInt(1), .constraint = @enumFromInt(2), .operand = Ref.none },
|
|
.{ .role = .input, .name = .empty, .constraint = @enumFromInt(3), .operand = Ref.fromIndex(5) },
|
|
};
|
|
const clobbers = [_]StringId{ @enumFromInt(4), @enumFromInt(6) };
|
|
const inst = Inst{
|
|
.op = .{ .inline_asm = .{
|
|
.template = @enumFromInt(10),
|
|
.operands = &operands,
|
|
.clobbers = &clobbers,
|
|
.has_side_effects = true,
|
|
} },
|
|
.ty = .i64,
|
|
};
|
|
switch (inst.op) {
|
|
.inline_asm => |a| {
|
|
try std.testing.expect(a.has_side_effects);
|
|
try std.testing.expectEqual(@as(usize, 2), a.operands.len);
|
|
try std.testing.expectEqual(@as(usize, 2), a.clobbers.len);
|
|
try std.testing.expectEqual(InlineAsm.AsmOperand.Role.out_value, a.operands[0].role);
|
|
// an out_value operand carries no input Ref — the asm yields it
|
|
try std.testing.expect(a.operands[0].operand.isNone());
|
|
try std.testing.expectEqual(InlineAsm.AsmOperand.Role.input, a.operands[1].role);
|
|
try std.testing.expectEqual(Ref.fromIndex(5), a.operands[1].operand);
|
|
// an anonymous operand uses the `.empty` StringId sentinel
|
|
try std.testing.expectEqual(StringId.empty, a.operands[1].name);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
test "function creation" {
|
|
const alloc = std.testing.allocator;
|
|
const params = &[_]Function.Param{
|
|
.{ .name = @enumFromInt(1), .ty = .i32 },
|
|
.{ .name = @enumFromInt(2), .ty = .i32 },
|
|
};
|
|
var func = Function.init(@enumFromInt(3), params, .i64);
|
|
defer func.deinit(alloc);
|
|
|
|
try std.testing.expectEqual(types.TypeId.i64, func.ret);
|
|
try std.testing.expectEqual(@as(usize, 2), func.params.len);
|
|
}
|