Merge remote-tracking branch 'origin/master' into llvm12
This commit is contained in:
@@ -200,7 +200,7 @@ test "FloatingPointRegister.toX" {
|
||||
|
||||
/// Represents an instruction in the AArch64 instruction set
|
||||
pub const Instruction = union(enum) {
|
||||
MoveWideImmediate: packed struct {
|
||||
move_wide_immediate: packed struct {
|
||||
rd: u5,
|
||||
imm16: u16,
|
||||
hw: u2,
|
||||
@@ -208,14 +208,14 @@ pub const Instruction = union(enum) {
|
||||
opc: u2,
|
||||
sf: u1,
|
||||
},
|
||||
PCRelativeAddress: packed struct {
|
||||
pc_relative_address: packed struct {
|
||||
rd: u5,
|
||||
immhi: u19,
|
||||
fixed: u5 = 0b10000,
|
||||
immlo: u2,
|
||||
op: u1,
|
||||
},
|
||||
LoadStoreRegister: packed struct {
|
||||
load_store_register: packed struct {
|
||||
rt: u5,
|
||||
rn: u5,
|
||||
offset: u12,
|
||||
@@ -225,7 +225,7 @@ pub const Instruction = union(enum) {
|
||||
fixed: u3 = 0b111,
|
||||
size: u2,
|
||||
},
|
||||
LoadStorePairOfRegisters: packed struct {
|
||||
load_store_register_pair: packed struct {
|
||||
rt1: u5,
|
||||
rn: u5,
|
||||
rt2: u5,
|
||||
@@ -235,20 +235,20 @@ pub const Instruction = union(enum) {
|
||||
fixed: u5 = 0b101_0_0,
|
||||
opc: u2,
|
||||
},
|
||||
LoadLiteral: packed struct {
|
||||
load_literal: packed struct {
|
||||
rt: u5,
|
||||
imm19: u19,
|
||||
fixed: u6 = 0b011_0_00,
|
||||
opc: u2,
|
||||
},
|
||||
ExceptionGeneration: packed struct {
|
||||
exception_generation: packed struct {
|
||||
ll: u2,
|
||||
op2: u3,
|
||||
imm16: u16,
|
||||
opc: u3,
|
||||
fixed: u8 = 0b1101_0100,
|
||||
},
|
||||
UnconditionalBranchRegister: packed struct {
|
||||
unconditional_branch_register: packed struct {
|
||||
op4: u5,
|
||||
rn: u5,
|
||||
op3: u6,
|
||||
@@ -256,15 +256,15 @@ pub const Instruction = union(enum) {
|
||||
opc: u4,
|
||||
fixed: u7 = 0b1101_011,
|
||||
},
|
||||
UnconditionalBranchImmediate: packed struct {
|
||||
unconditional_branch_immediate: packed struct {
|
||||
imm26: u26,
|
||||
fixed: u5 = 0b00101,
|
||||
op: u1,
|
||||
},
|
||||
NoOperation: packed struct {
|
||||
no_operation: packed struct {
|
||||
fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111,
|
||||
},
|
||||
LogicalShiftedRegister: packed struct {
|
||||
logical_shifted_register: packed struct {
|
||||
rd: u5,
|
||||
rn: u5,
|
||||
imm6: u6,
|
||||
@@ -275,7 +275,7 @@ pub const Instruction = union(enum) {
|
||||
opc: u2,
|
||||
sf: u1,
|
||||
},
|
||||
AddSubtractImmediate: packed struct {
|
||||
add_subtract_immediate: packed struct {
|
||||
rd: u5,
|
||||
rn: u5,
|
||||
imm12: u12,
|
||||
@@ -285,6 +285,20 @@ pub const Instruction = union(enum) {
|
||||
op: u1,
|
||||
sf: u1,
|
||||
},
|
||||
conditional_branch: struct {
|
||||
cond: u4,
|
||||
o0: u1,
|
||||
imm19: u19,
|
||||
o1: u1,
|
||||
fixed: u7 = 0b0101010,
|
||||
},
|
||||
compare_and_branch: struct {
|
||||
rt: u5,
|
||||
imm19: u19,
|
||||
op: u1,
|
||||
fixed: u6 = 0b011010,
|
||||
sf: u1,
|
||||
},
|
||||
|
||||
pub const Shift = struct {
|
||||
shift: Type = .lsl,
|
||||
@@ -303,19 +317,73 @@ pub const Instruction = union(enum) {
|
||||
};
|
||||
};
|
||||
|
||||
pub const Condition = enum(u4) {
|
||||
/// Integer: Equal
|
||||
/// Floating point: Equal
|
||||
eq,
|
||||
/// Integer: Not equal
|
||||
/// Floating point: Not equal or unordered
|
||||
ne,
|
||||
/// Integer: Carry set
|
||||
/// Floating point: Greater than, equal, or unordered
|
||||
cs,
|
||||
/// Integer: Carry clear
|
||||
/// Floating point: Less than
|
||||
cc,
|
||||
/// Integer: Minus, negative
|
||||
/// Floating point: Less than
|
||||
mi,
|
||||
/// Integer: Plus, positive or zero
|
||||
/// Floating point: Greater than, equal, or unordered
|
||||
pl,
|
||||
/// Integer: Overflow
|
||||
/// Floating point: Unordered
|
||||
vs,
|
||||
/// Integer: No overflow
|
||||
/// Floating point: Ordered
|
||||
vc,
|
||||
/// Integer: Unsigned higher
|
||||
/// Floating point: Greater than, or unordered
|
||||
hi,
|
||||
/// Integer: Unsigned lower or same
|
||||
/// Floating point: Less than or equal
|
||||
ls,
|
||||
/// Integer: Signed greater than or equal
|
||||
/// Floating point: Greater than or equal
|
||||
ge,
|
||||
/// Integer: Signed less than
|
||||
/// Floating point: Less than, or unordered
|
||||
lt,
|
||||
/// Integer: Signed greater than
|
||||
/// Floating point: Greater than
|
||||
gt,
|
||||
/// Integer: Signed less than or equal
|
||||
/// Floating point: Less than, equal, or unordered
|
||||
le,
|
||||
/// Integer: Always
|
||||
/// Floating point: Always
|
||||
al,
|
||||
/// Integer: Always
|
||||
/// Floating point: Always
|
||||
nv,
|
||||
};
|
||||
|
||||
pub fn toU32(self: Instruction) u32 {
|
||||
return switch (self) {
|
||||
.MoveWideImmediate => |v| @bitCast(u32, v),
|
||||
.PCRelativeAddress => |v| @bitCast(u32, v),
|
||||
.LoadStoreRegister => |v| @bitCast(u32, v),
|
||||
.LoadStorePairOfRegisters => |v| @bitCast(u32, v),
|
||||
.LoadLiteral => |v| @bitCast(u32, v),
|
||||
.ExceptionGeneration => |v| @bitCast(u32, v),
|
||||
.UnconditionalBranchRegister => |v| @bitCast(u32, v),
|
||||
.UnconditionalBranchImmediate => |v| @bitCast(u32, v),
|
||||
.NoOperation => |v| @bitCast(u32, v),
|
||||
.LogicalShiftedRegister => |v| @bitCast(u32, v),
|
||||
.AddSubtractImmediate => |v| @bitCast(u32, v),
|
||||
.move_wide_immediate => |v| @bitCast(u32, v),
|
||||
.pc_relative_address => |v| @bitCast(u32, v),
|
||||
.load_store_register => |v| @bitCast(u32, v),
|
||||
.load_store_register_pair => |v| @bitCast(u32, v),
|
||||
.load_literal => |v| @bitCast(u32, v),
|
||||
.exception_generation => |v| @bitCast(u32, v),
|
||||
.unconditional_branch_register => |v| @bitCast(u32, v),
|
||||
.unconditional_branch_immediate => |v| @bitCast(u32, v),
|
||||
.no_operation => |v| @bitCast(u32, v),
|
||||
.logical_shifted_register => |v| @bitCast(u32, v),
|
||||
.add_subtract_immediate => |v| @bitCast(u32, v),
|
||||
// TODO once packed structs work, this can be refactored
|
||||
.conditional_branch => |v| @as(u32, v.cond) | (@as(u32, v.o0) << 4) | (@as(u32, v.imm19) << 5) | (@as(u32, v.o1) << 24) | (@as(u32, v.fixed) << 25),
|
||||
.compare_and_branch => |v| @as(u32, v.rt) | (@as(u32, v.imm19) << 5) | (@as(u32, v.op) << 24) | (@as(u32, v.fixed) << 25) | (@as(u32, v.sf) << 31),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -329,7 +397,7 @@ pub const Instruction = union(enum) {
|
||||
32 => {
|
||||
assert(shift % 16 == 0 and shift <= 16);
|
||||
return Instruction{
|
||||
.MoveWideImmediate = .{
|
||||
.move_wide_immediate = .{
|
||||
.rd = rd.id(),
|
||||
.imm16 = imm16,
|
||||
.hw = @intCast(u2, shift / 16),
|
||||
@@ -341,7 +409,7 @@ pub const Instruction = union(enum) {
|
||||
64 => {
|
||||
assert(shift % 16 == 0 and shift <= 48);
|
||||
return Instruction{
|
||||
.MoveWideImmediate = .{
|
||||
.move_wide_immediate = .{
|
||||
.rd = rd.id(),
|
||||
.imm16 = imm16,
|
||||
.hw = @intCast(u2, shift / 16),
|
||||
@@ -358,7 +426,7 @@ pub const Instruction = union(enum) {
|
||||
assert(rd.size() == 64);
|
||||
const imm21_u = @bitCast(u21, imm21);
|
||||
return Instruction{
|
||||
.PCRelativeAddress = .{
|
||||
.pc_relative_address = .{
|
||||
.rd = rd.id(),
|
||||
.immlo = @truncate(u2, imm21_u),
|
||||
.immhi = @truncate(u19, imm21_u >> 2),
|
||||
@@ -487,11 +555,17 @@ pub const Instruction = union(enum) {
|
||||
/// Which kind of load/store to perform
|
||||
const LoadStoreVariant = enum {
|
||||
/// 32-bit or 64-bit
|
||||
normal,
|
||||
/// 16-bit
|
||||
half,
|
||||
/// 8-bit
|
||||
byte,
|
||||
str,
|
||||
/// 16-bit, zero-extended
|
||||
strh,
|
||||
/// 8-bit, zero-extended
|
||||
strb,
|
||||
/// 32-bit or 64-bit
|
||||
ldr,
|
||||
/// 16-bit, zero-extended
|
||||
ldrh,
|
||||
/// 8-bit, zero-extended
|
||||
ldrb,
|
||||
};
|
||||
|
||||
fn loadStoreRegister(
|
||||
@@ -499,7 +573,6 @@ pub const Instruction = union(enum) {
|
||||
rn: Register,
|
||||
offset: LoadStoreOffset,
|
||||
variant: LoadStoreVariant,
|
||||
load: bool,
|
||||
) Instruction {
|
||||
const off = offset.toU12();
|
||||
const op1: u2 = blk: {
|
||||
@@ -512,9 +585,12 @@ pub const Instruction = union(enum) {
|
||||
}
|
||||
break :blk 0b00;
|
||||
};
|
||||
const opc: u2 = if (load) 0b01 else 0b00;
|
||||
const opc: u2 = switch (variant) {
|
||||
.ldr, .ldrh, .ldrb => 0b01,
|
||||
.str, .strh, .strb => 0b00,
|
||||
};
|
||||
return Instruction{
|
||||
.LoadStoreRegister = .{
|
||||
.load_store_register = .{
|
||||
.rt = rt.id(),
|
||||
.rn = rn.id(),
|
||||
.offset = off,
|
||||
@@ -523,20 +599,20 @@ pub const Instruction = union(enum) {
|
||||
.v = 0,
|
||||
.size = blk: {
|
||||
switch (variant) {
|
||||
.normal => switch (rt.size()) {
|
||||
.ldr, .str => switch (rt.size()) {
|
||||
32 => break :blk 0b10,
|
||||
64 => break :blk 0b11,
|
||||
else => unreachable, // unexpected register size
|
||||
},
|
||||
.half => break :blk 0b01,
|
||||
.byte => break :blk 0b00,
|
||||
.ldrh, .strh => break :blk 0b01,
|
||||
.ldrb, .strb => break :blk 0b00,
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn loadStorePairOfRegisters(
|
||||
fn loadStoreRegisterPair(
|
||||
rt1: Register,
|
||||
rt2: Register,
|
||||
rn: Register,
|
||||
@@ -549,7 +625,7 @@ pub const Instruction = union(enum) {
|
||||
assert(-256 <= offset and offset <= 252);
|
||||
const imm7 = @truncate(u7, @bitCast(u9, offset >> 2));
|
||||
return Instruction{
|
||||
.LoadStorePairOfRegisters = .{
|
||||
.load_store_register_pair = .{
|
||||
.rt1 = rt1.id(),
|
||||
.rn = rn.id(),
|
||||
.rt2 = rt2.id(),
|
||||
@@ -564,7 +640,7 @@ pub const Instruction = union(enum) {
|
||||
assert(-512 <= offset and offset <= 504);
|
||||
const imm7 = @truncate(u7, @bitCast(u9, offset >> 3));
|
||||
return Instruction{
|
||||
.LoadStorePairOfRegisters = .{
|
||||
.load_store_register_pair = .{
|
||||
.rt1 = rt1.id(),
|
||||
.rn = rn.id(),
|
||||
.rt2 = rt2.id(),
|
||||
@@ -583,7 +659,7 @@ pub const Instruction = union(enum) {
|
||||
switch (rt.size()) {
|
||||
32 => {
|
||||
return Instruction{
|
||||
.LoadLiteral = .{
|
||||
.load_literal = .{
|
||||
.rt = rt.id(),
|
||||
.imm19 = imm19,
|
||||
.opc = 0b00,
|
||||
@@ -592,7 +668,7 @@ pub const Instruction = union(enum) {
|
||||
},
|
||||
64 => {
|
||||
return Instruction{
|
||||
.LoadLiteral = .{
|
||||
.load_literal = .{
|
||||
.rt = rt.id(),
|
||||
.imm19 = imm19,
|
||||
.opc = 0b01,
|
||||
@@ -610,7 +686,7 @@ pub const Instruction = union(enum) {
|
||||
imm16: u16,
|
||||
) Instruction {
|
||||
return Instruction{
|
||||
.ExceptionGeneration = .{
|
||||
.exception_generation = .{
|
||||
.ll = ll,
|
||||
.op2 = op2,
|
||||
.imm16 = imm16,
|
||||
@@ -629,7 +705,7 @@ pub const Instruction = union(enum) {
|
||||
assert(rn.size() == 64);
|
||||
|
||||
return Instruction{
|
||||
.UnconditionalBranchRegister = .{
|
||||
.unconditional_branch_register = .{
|
||||
.op4 = op4,
|
||||
.rn = rn.id(),
|
||||
.op3 = op3,
|
||||
@@ -644,7 +720,7 @@ pub const Instruction = union(enum) {
|
||||
offset: i28,
|
||||
) Instruction {
|
||||
return Instruction{
|
||||
.UnconditionalBranchImmediate = .{
|
||||
.unconditional_branch_immediate = .{
|
||||
.imm26 = @bitCast(u26, @intCast(i26, offset >> 2)),
|
||||
.op = op,
|
||||
},
|
||||
@@ -663,7 +739,7 @@ pub const Instruction = union(enum) {
|
||||
32 => {
|
||||
assert(shift.amount < 32);
|
||||
return Instruction{
|
||||
.LogicalShiftedRegister = .{
|
||||
.logical_shifted_register = .{
|
||||
.rd = rd.id(),
|
||||
.rn = rn.id(),
|
||||
.imm6 = shift.amount,
|
||||
@@ -677,7 +753,7 @@ pub const Instruction = union(enum) {
|
||||
},
|
||||
64 => {
|
||||
return Instruction{
|
||||
.LogicalShiftedRegister = .{
|
||||
.logical_shifted_register = .{
|
||||
.rd = rd.id(),
|
||||
.rn = rn.id(),
|
||||
.imm6 = shift.amount,
|
||||
@@ -702,7 +778,7 @@ pub const Instruction = union(enum) {
|
||||
shift: bool,
|
||||
) Instruction {
|
||||
return Instruction{
|
||||
.AddSubtractImmediate = .{
|
||||
.add_subtract_immediate = .{
|
||||
.rd = rd.id(),
|
||||
.rn = rn.id(),
|
||||
.imm12 = imm12,
|
||||
@@ -718,6 +794,43 @@ pub const Instruction = union(enum) {
|
||||
};
|
||||
}
|
||||
|
||||
fn conditionalBranch(
|
||||
o0: u1,
|
||||
o1: u1,
|
||||
cond: Condition,
|
||||
offset: i21,
|
||||
) Instruction {
|
||||
assert(offset & 0b11 == 0b00);
|
||||
return Instruction{
|
||||
.conditional_branch = .{
|
||||
.cond = @enumToInt(cond),
|
||||
.o0 = o0,
|
||||
.imm19 = @bitCast(u19, @intCast(i19, offset >> 2)),
|
||||
.o1 = o1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn compareAndBranch(
|
||||
op: u1,
|
||||
rt: Register,
|
||||
offset: i21,
|
||||
) Instruction {
|
||||
assert(offset & 0b11 == 0b00);
|
||||
return Instruction{
|
||||
.compare_and_branch = .{
|
||||
.rt = rt.id(),
|
||||
.imm19 = @bitCast(u19, @intCast(i19, offset >> 2)),
|
||||
.op = op,
|
||||
.sf = switch (rt.size()) {
|
||||
32 => 0b0,
|
||||
64 => 0b1,
|
||||
else => unreachable, // unexpected register size
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Helper functions for assembly syntax functions
|
||||
|
||||
// Move wide (immediate)
|
||||
@@ -756,25 +869,33 @@ pub const Instruction = union(enum) {
|
||||
|
||||
pub fn ldr(rt: Register, args: LdrArgs) Instruction {
|
||||
switch (args) {
|
||||
.register => |info| return loadStoreRegister(rt, info.rn, info.offset, .normal, true),
|
||||
.register => |info| return loadStoreRegister(rt, info.rn, info.offset, .ldr),
|
||||
.literal => |literal| return loadLiteral(rt, literal),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ldrh(rt: Register, rn: Register, args: StrArgs) Instruction {
|
||||
return loadStoreRegister(rt, rn, args.offset, .ldrh);
|
||||
}
|
||||
|
||||
pub fn ldrb(rt: Register, rn: Register, args: StrArgs) Instruction {
|
||||
return loadStoreRegister(rt, rn, args.offset, .ldrb);
|
||||
}
|
||||
|
||||
pub const StrArgs = struct {
|
||||
offset: LoadStoreOffset = LoadStoreOffset.none,
|
||||
};
|
||||
|
||||
pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction {
|
||||
return loadStoreRegister(rt, rn, args.offset, .normal, false);
|
||||
return loadStoreRegister(rt, rn, args.offset, .str);
|
||||
}
|
||||
|
||||
pub fn strh(rt: Register, rn: Register, args: StrArgs) Instruction {
|
||||
return loadStoreRegister(rt, rn, args.offset, .half, false);
|
||||
return loadStoreRegister(rt, rn, args.offset, .strh);
|
||||
}
|
||||
|
||||
pub fn strb(rt: Register, rn: Register, args: StrArgs) Instruction {
|
||||
return loadStoreRegister(rt, rn, args.offset, .byte, false);
|
||||
return loadStoreRegister(rt, rn, args.offset, .strb);
|
||||
}
|
||||
|
||||
// Load or store pair of registers
|
||||
@@ -805,19 +926,19 @@ pub const Instruction = union(enum) {
|
||||
};
|
||||
|
||||
pub fn ldp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction {
|
||||
return loadStorePairOfRegisters(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), true);
|
||||
return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), true);
|
||||
}
|
||||
|
||||
pub fn ldnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
|
||||
return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, true);
|
||||
return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, true);
|
||||
}
|
||||
|
||||
pub fn stp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction {
|
||||
return loadStorePairOfRegisters(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), false);
|
||||
return loadStoreRegisterPair(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), false);
|
||||
}
|
||||
|
||||
pub fn stnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
|
||||
return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, false);
|
||||
return loadStoreRegisterPair(rt1, rt2, rn, offset, 0, false);
|
||||
}
|
||||
|
||||
// Exception generation
|
||||
@@ -869,7 +990,7 @@ pub const Instruction = union(enum) {
|
||||
// Nop
|
||||
|
||||
pub fn nop() Instruction {
|
||||
return Instruction{ .NoOperation = .{} };
|
||||
return Instruction{ .no_operation = .{} };
|
||||
}
|
||||
|
||||
// Logical (shifted register)
|
||||
@@ -923,6 +1044,22 @@ pub const Instruction = union(enum) {
|
||||
pub fn subs(rd: Register, rn: Register, imm: u12, shift: bool) Instruction {
|
||||
return addSubtractImmediate(0b1, 0b1, rd, rn, imm, shift);
|
||||
}
|
||||
|
||||
// Conditional branch
|
||||
|
||||
pub fn bCond(cond: Condition, offset: i21) Instruction {
|
||||
return conditionalBranch(0b0, 0b0, cond, offset);
|
||||
}
|
||||
|
||||
// Compare and branch
|
||||
|
||||
pub fn cbz(rt: Register, offset: i21) Instruction {
|
||||
return compareAndBranch(0b0, rt, offset);
|
||||
}
|
||||
|
||||
pub fn cbnz(rt: Register, offset: i21) Instruction {
|
||||
return compareAndBranch(0b1, rt, offset);
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
@@ -1004,6 +1141,14 @@ test "serialize instructions" {
|
||||
.inst = Instruction.ldr(.x2, .{ .literal = 0x1 }),
|
||||
.expected = 0b01_011_0_00_0000000000000000001_00010,
|
||||
},
|
||||
.{ // ldrh x7, [x4], #0xaa
|
||||
.inst = Instruction.ldrh(.x7, .x4, .{ .offset = Instruction.LoadStoreOffset.imm_post_index(0xaa) }),
|
||||
.expected = 0b01_111_0_00_01_0_010101010_01_00100_00111,
|
||||
},
|
||||
.{ // ldrb x9, [x15, #0xff]!
|
||||
.inst = Instruction.ldrb(.x9, .x15, .{ .offset = Instruction.LoadStoreOffset.imm_pre_index(0xff) }),
|
||||
.expected = 0b00_111_0_00_01_0_011111111_11_01111_01001,
|
||||
},
|
||||
.{ // str x2, [x1]
|
||||
.inst = Instruction.str(.x2, .x1, .{}),
|
||||
.expected = 0b11_111_0_01_00_000000000000_00001_00010,
|
||||
@@ -1068,6 +1213,14 @@ test "serialize instructions" {
|
||||
.inst = Instruction.subs(.x0, .x5, 11, true),
|
||||
.expected = 0b1_1_1_100010_1_0000_0000_1011_00101_00000,
|
||||
},
|
||||
.{ // b.hi #-4
|
||||
.inst = Instruction.bCond(.hi, -4),
|
||||
.expected = 0b0101010_0_1111111111111111111_0_1000,
|
||||
},
|
||||
.{ // cbz x10, #40
|
||||
.inst = Instruction.cbz(.x10, 40),
|
||||
.expected = 0b1_011010_0_0000000000000001010_01010,
|
||||
},
|
||||
};
|
||||
|
||||
for (testcases) |case| {
|
||||
|
||||
@@ -44,24 +44,36 @@ fn formatTypeAsCIdentifier(
|
||||
var buffer = [1]u8{0} ** 128;
|
||||
// We don't care if it gets cut off, it's still more unique than a number
|
||||
var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer;
|
||||
|
||||
for (buf) |c, i| {
|
||||
switch (c) {
|
||||
0 => return writer.writeAll(buf[0..i]),
|
||||
'a'...'z', 'A'...'Z', '_', '$' => {},
|
||||
'0'...'9' => if (i == 0) {
|
||||
buf[i] = '_';
|
||||
},
|
||||
else => buf[i] = '_',
|
||||
}
|
||||
}
|
||||
return writer.writeAll(buf);
|
||||
return formatIdent(buf, "", .{}, writer);
|
||||
}
|
||||
|
||||
pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) {
|
||||
return .{ .data = t };
|
||||
}
|
||||
|
||||
fn formatIdent(
|
||||
ident: []const u8,
|
||||
comptime fmt: []const u8,
|
||||
options: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
for (ident) |c, i| {
|
||||
switch (c) {
|
||||
'a'...'z', 'A'...'Z', '_' => try writer.writeByte(c),
|
||||
'0'...'9' => if (i == 0) {
|
||||
try writer.print("${x:2}", .{c});
|
||||
} else {
|
||||
try writer.writeByte(c);
|
||||
},
|
||||
else => try writer.print("${x:2}", .{c}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmtIdent(ident: []const u8) std.fmt.Formatter(formatIdent) {
|
||||
return .{ .data = ident };
|
||||
}
|
||||
|
||||
/// This data is available when outputting .c code for a Module.
|
||||
/// It is not available when generating .h file.
|
||||
pub const Object = struct {
|
||||
@@ -160,7 +172,10 @@ pub const DeclGen = struct {
|
||||
val: Value,
|
||||
) error{ OutOfMemory, AnalysisFail }!void {
|
||||
if (val.isUndef()) {
|
||||
return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{});
|
||||
// This should lower to 0xaa bytes in safe modes, and for unsafe modes should
|
||||
// lower to leaving variables uninitialized (that might need to be implemented
|
||||
// outside of this function).
|
||||
return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement renderValue undef", .{});
|
||||
}
|
||||
switch (t.zigTypeTag()) {
|
||||
.Int => {
|
||||
@@ -276,6 +291,31 @@ pub const DeclGen = struct {
|
||||
try writer.writeAll(", .error = 0 }");
|
||||
}
|
||||
},
|
||||
.Enum => {
|
||||
switch (val.tag()) {
|
||||
.enum_field_index => {
|
||||
const field_index = val.castTag(.enum_field_index).?.data;
|
||||
switch (t.tag()) {
|
||||
.enum_simple => return writer.print("{d}", .{field_index}),
|
||||
.enum_full, .enum_nonexhaustive => {
|
||||
const enum_full = t.cast(Type.Payload.EnumFull).?.data;
|
||||
if (enum_full.values.count() != 0) {
|
||||
const tag_val = enum_full.values.entries.items[field_index].key;
|
||||
return dg.renderValue(writer, enum_full.tag_ty, tag_val);
|
||||
} else {
|
||||
return writer.print("{d}", .{field_index});
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
else => {
|
||||
var int_tag_ty_buffer: Type.Payload.Bits = undefined;
|
||||
const int_tag_ty = t.intTagType(&int_tag_ty_buffer);
|
||||
return dg.renderValue(writer, int_tag_ty, val);
|
||||
},
|
||||
}
|
||||
},
|
||||
else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement value {s}", .{
|
||||
@tagName(e),
|
||||
}),
|
||||
@@ -356,6 +396,9 @@ pub const DeclGen = struct {
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
|
||||
.Float => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Float", .{}),
|
||||
|
||||
.Pointer => {
|
||||
if (t.isSlice()) {
|
||||
return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement slices", .{});
|
||||
@@ -430,10 +473,59 @@ pub const DeclGen = struct {
|
||||
try w.writeAll(name);
|
||||
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
||||
},
|
||||
.Null, .Undefined => unreachable, // must be const or comptime
|
||||
else => |e| return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type {s}", .{
|
||||
@tagName(e),
|
||||
}),
|
||||
.Struct => {
|
||||
if (dg.typedefs.get(t)) |some| {
|
||||
return w.writeAll(some.name);
|
||||
}
|
||||
const struct_obj = t.castTag(.@"struct").?.data; // Handle 0 bit types elsewhere.
|
||||
const fqn = try struct_obj.getFullyQualifiedName(dg.typedefs.allocator);
|
||||
defer dg.typedefs.allocator.free(fqn);
|
||||
|
||||
var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
|
||||
defer buffer.deinit();
|
||||
|
||||
try buffer.appendSlice("typedef struct {\n");
|
||||
for (struct_obj.fields.entries.items) |entry| {
|
||||
try buffer.append(' ');
|
||||
try dg.renderType(buffer.writer(), entry.value.ty);
|
||||
try buffer.writer().print(" {s};\n", .{fmtIdent(entry.key)});
|
||||
}
|
||||
try buffer.appendSlice("} ");
|
||||
|
||||
const name_start = buffer.items.len;
|
||||
try buffer.writer().print("zig_S_{s};\n", .{fmtIdent(fqn)});
|
||||
|
||||
const rendered = buffer.toOwnedSlice();
|
||||
errdefer dg.typedefs.allocator.free(rendered);
|
||||
const name = rendered[name_start .. rendered.len - 2];
|
||||
|
||||
try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
|
||||
try w.writeAll(name);
|
||||
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
||||
},
|
||||
.Enum => {
|
||||
// For enums, we simply use the integer tag type.
|
||||
var int_tag_ty_buffer: Type.Payload.Bits = undefined;
|
||||
const int_tag_ty = t.intTagType(&int_tag_ty_buffer);
|
||||
|
||||
try dg.renderType(w, int_tag_ty);
|
||||
},
|
||||
.Union => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Union", .{}),
|
||||
.Fn => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Fn", .{}),
|
||||
.Opaque => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Opaque", .{}),
|
||||
.Frame => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Frame", .{}),
|
||||
.AnyFrame => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type AnyFrame", .{}),
|
||||
.Vector => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Vector", .{}),
|
||||
|
||||
.Null,
|
||||
.Undefined,
|
||||
.EnumLiteral,
|
||||
.ComptimeFloat,
|
||||
.ComptimeInt,
|
||||
.Type,
|
||||
=> unreachable, // must be const or comptime
|
||||
|
||||
.BoundFn => unreachable, // this type will be deleted from the language
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,8 +617,26 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
|
||||
|
||||
for (body.instructions) |inst| {
|
||||
const result_value = switch (inst.tag) {
|
||||
.constant => unreachable, // excluded from function bodies
|
||||
// TODO use a different strategy for add that communicates to the optimizer
|
||||
// that wrapping is UB.
|
||||
.add => try genBinOp(o, inst.castTag(.add).?, " + "),
|
||||
// TODO make this do wrapping arithmetic for signed ints
|
||||
.addwrap => try genBinOp(o, inst.castTag(.add).?, " + "),
|
||||
// TODO use a different strategy for sub that communicates to the optimizer
|
||||
// that wrapping is UB.
|
||||
.sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
|
||||
// TODO make this do wrapping arithmetic for signed ints
|
||||
.subwrap => try genBinOp(o, inst.castTag(.sub).?, " - "),
|
||||
// TODO use a different strategy for mul that communicates to the optimizer
|
||||
// that wrapping is UB.
|
||||
.mul => try genBinOp(o, inst.castTag(.sub).?, " * "),
|
||||
// TODO make this do wrapping multiplication for signed ints
|
||||
.mulwrap => try genBinOp(o, inst.castTag(.sub).?, " * "),
|
||||
// TODO use a different strategy for div that communicates to the optimizer
|
||||
// that wrapping is UB.
|
||||
.div => try genBinOp(o, inst.castTag(.div).?, " / "),
|
||||
|
||||
.constant => unreachable, // excluded from function bodies
|
||||
.alloc => try genAlloc(o, inst.castTag(.alloc).?),
|
||||
.arg => genArg(o),
|
||||
.assembly => try genAsm(o, inst.castTag(.assembly).?),
|
||||
@@ -546,7 +656,6 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
|
||||
.ret => try genRet(o, inst.castTag(.ret).?),
|
||||
.retvoid => try genRetVoid(o),
|
||||
.store => try genStore(o, inst.castTag(.store).?),
|
||||
.sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
|
||||
.unreach => try genUnreach(o, inst.castTag(.unreach).?),
|
||||
.loop => try genLoop(o, inst.castTag(.loop).?),
|
||||
.condbr => try genCondBr(o, inst.castTag(.condbr).?),
|
||||
@@ -567,17 +676,24 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
|
||||
.wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?),
|
||||
.optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?),
|
||||
.optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?),
|
||||
.ref => try genRef(o, inst.castTag(.ref).?),
|
||||
.struct_field_ptr => try genStructFieldPtr(o, inst.castTag(.struct_field_ptr).?),
|
||||
|
||||
.is_err => try genIsErr(o, inst.castTag(.is_err).?),
|
||||
.is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?),
|
||||
.error_to_int => try genErrorToInt(o, inst.castTag(.error_to_int).?),
|
||||
.int_to_error => try genIntToError(o, inst.castTag(.int_to_error).?),
|
||||
|
||||
.unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?),
|
||||
.unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?),
|
||||
.unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?),
|
||||
.unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?),
|
||||
.wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?),
|
||||
.wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?),
|
||||
else => |e| return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for {}", .{e}),
|
||||
.br_block_flat => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for br_block_flat", .{}),
|
||||
.ptrtoint => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for ptrtoint", .{}),
|
||||
.varptr => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for varptr", .{}),
|
||||
.floatcast => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for floatcast", .{}),
|
||||
};
|
||||
switch (result_value) {
|
||||
.none => {},
|
||||
@@ -996,6 +1112,37 @@ fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
return local;
|
||||
}
|
||||
|
||||
fn genRef(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.writeAll(" = ");
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.writeAll(";\n");
|
||||
return local;
|
||||
}
|
||||
|
||||
fn genStructFieldPtr(o: *Object, inst: *Inst.StructFieldPtr) !CValue {
|
||||
const writer = o.writer();
|
||||
const struct_ptr = try o.resolveInst(inst.struct_ptr);
|
||||
const struct_obj = inst.struct_ptr.ty.elemType().castTag(.@"struct").?.data;
|
||||
const field_name = struct_obj.fields.entries.items[inst.field_index].key;
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
switch (struct_ptr) {
|
||||
.local_ref => |i| {
|
||||
try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) });
|
||||
},
|
||||
else => {
|
||||
try writer.writeAll(" = &");
|
||||
try o.writeCValue(writer, struct_ptr);
|
||||
try writer.print("->{};\n", .{fmtIdent(field_name)});
|
||||
},
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
// *(E!T) -> E NOT *E
|
||||
fn genUnwrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
@@ -1088,7 +1235,7 @@ fn IndentWriter(comptime UnderlyingWriter: type) type {
|
||||
pub const Error = UnderlyingWriter.Error;
|
||||
pub const Writer = std.io.Writer(*Self, Error, write);
|
||||
|
||||
pub const indent_delta = 4;
|
||||
pub const indent_delta = 1;
|
||||
|
||||
underlying_writer: UnderlyingWriter,
|
||||
indent_count: usize = 0,
|
||||
|
||||
@@ -76,7 +76,6 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 {
|
||||
.spirv32 => return error.LLVMBackendDoesNotSupportSPIRV,
|
||||
.spirv64 => return error.LLVMBackendDoesNotSupportSPIRV,
|
||||
};
|
||||
// TODO Add a sub-arch for some architectures depending on CPU features.
|
||||
|
||||
const llvm_os = switch (target.os.tag) {
|
||||
.freestanding => "unknown",
|
||||
|
||||
@@ -2,6 +2,7 @@ const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArrayList = std.ArrayList;
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const leb = std.leb;
|
||||
const mem = std.mem;
|
||||
const wasm = std.wasm;
|
||||
@@ -15,9 +16,12 @@ const Value = @import("../value.zig").Value;
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const AnyMCValue = @import("../codegen.zig").AnyMCValue;
|
||||
const LazySrcLoc = Module.LazySrcLoc;
|
||||
const link = @import("../link.zig");
|
||||
const TypedValue = @import("../TypedValue.zig");
|
||||
|
||||
/// Wasm Value, created when generating an instruction
|
||||
const WValue = union(enum) {
|
||||
/// May be referenced but is unused
|
||||
none: void,
|
||||
/// Index of the local variable
|
||||
local: u32,
|
||||
@@ -29,6 +33,450 @@ const WValue = union(enum) {
|
||||
block_idx: u32,
|
||||
};
|
||||
|
||||
/// Wasm ops, but without input/output/signedness information
|
||||
/// Used for `buildOpcode`
|
||||
const Op = enum {
|
||||
@"unreachable",
|
||||
nop,
|
||||
block,
|
||||
loop,
|
||||
@"if",
|
||||
@"else",
|
||||
end,
|
||||
br,
|
||||
br_if,
|
||||
br_table,
|
||||
@"return",
|
||||
call,
|
||||
call_indirect,
|
||||
drop,
|
||||
select,
|
||||
local_get,
|
||||
local_set,
|
||||
local_tee,
|
||||
global_get,
|
||||
global_set,
|
||||
load,
|
||||
store,
|
||||
memory_size,
|
||||
memory_grow,
|
||||
@"const",
|
||||
eqz,
|
||||
eq,
|
||||
ne,
|
||||
lt,
|
||||
gt,
|
||||
le,
|
||||
ge,
|
||||
clz,
|
||||
ctz,
|
||||
popcnt,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
div,
|
||||
rem,
|
||||
@"and",
|
||||
@"or",
|
||||
xor,
|
||||
shl,
|
||||
shr,
|
||||
rotl,
|
||||
rotr,
|
||||
abs,
|
||||
neg,
|
||||
ceil,
|
||||
floor,
|
||||
trunc,
|
||||
nearest,
|
||||
sqrt,
|
||||
min,
|
||||
max,
|
||||
copysign,
|
||||
wrap,
|
||||
convert,
|
||||
demote,
|
||||
promote,
|
||||
reinterpret,
|
||||
extend,
|
||||
};
|
||||
|
||||
/// Contains the settings needed to create an `Opcode` using `buildOpcode`.
|
||||
///
|
||||
/// The fields correspond to the opcode name. Here is an example
|
||||
/// i32_trunc_f32_s
|
||||
/// ^ ^ ^ ^
|
||||
/// | | | |
|
||||
/// valtype1 | | |
|
||||
/// = .i32 | | |
|
||||
/// | | |
|
||||
/// op | |
|
||||
/// = .trunc | |
|
||||
/// | |
|
||||
/// valtype2 |
|
||||
/// = .f32 |
|
||||
/// |
|
||||
/// width |
|
||||
/// = null |
|
||||
/// |
|
||||
/// signed
|
||||
/// = true
|
||||
///
|
||||
/// There can be missing fields, here are some more examples:
|
||||
/// i64_load8_u
|
||||
/// --> .{ .valtype1 = .i64, .op = .load, .width = 8, signed = false }
|
||||
/// i32_mul
|
||||
/// --> .{ .valtype1 = .i32, .op = .trunc }
|
||||
/// nop
|
||||
/// --> .{ .op = .nop }
|
||||
const OpcodeBuildArguments = struct {
|
||||
/// First valtype in the opcode (usually represents the type of the output)
|
||||
valtype1: ?wasm.Valtype = null,
|
||||
/// The operation (e.g. call, unreachable, div, min, sqrt, etc.)
|
||||
op: Op,
|
||||
/// Width of the operation (e.g. 8 for i32_load8_s, 16 for i64_extend16_i32_s)
|
||||
width: ?u8 = null,
|
||||
/// Second valtype in the opcode name (usually represents the type of the input)
|
||||
valtype2: ?wasm.Valtype = null,
|
||||
/// Signedness of the op
|
||||
signedness: ?std.builtin.Signedness = null,
|
||||
};
|
||||
|
||||
/// Helper function that builds an Opcode given the arguments needed
|
||||
fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
|
||||
switch (args.op) {
|
||||
.@"unreachable" => return .@"unreachable",
|
||||
.nop => return .nop,
|
||||
.block => return .block,
|
||||
.loop => return .loop,
|
||||
.@"if" => return .@"if",
|
||||
.@"else" => return .@"else",
|
||||
.end => return .end,
|
||||
.br => return .br,
|
||||
.br_if => return .br_if,
|
||||
.br_table => return .br_table,
|
||||
.@"return" => return .@"return",
|
||||
.call => return .call,
|
||||
.call_indirect => return .call_indirect,
|
||||
.drop => return .drop,
|
||||
.select => return .select,
|
||||
.local_get => return .local_get,
|
||||
.local_set => return .local_set,
|
||||
.local_tee => return .local_tee,
|
||||
.global_get => return .global_get,
|
||||
.global_set => return .global_set,
|
||||
|
||||
.load => if (args.width) |width| switch (width) {
|
||||
8 => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_load8_s else return .i32_load8_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_load8_s else return .i64_load8_u,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
16 => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_load16_s else return .i32_load16_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_load16_s else return .i64_load16_u,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
32 => switch (args.valtype1.?) {
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_load32_s else return .i64_load32_u,
|
||||
.i32, .f32, .f64 => unreachable,
|
||||
},
|
||||
else => unreachable,
|
||||
} else switch (args.valtype1.?) {
|
||||
.i32 => return .i32_load,
|
||||
.i64 => return .i64_load,
|
||||
.f32 => return .f32_load,
|
||||
.f64 => return .f64_load,
|
||||
},
|
||||
.store => if (args.width) |width| {
|
||||
switch (width) {
|
||||
8 => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_store8,
|
||||
.i64 => return .i64_store8,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
16 => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_store16,
|
||||
.i64 => return .i64_store16,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
32 => switch (args.valtype1.?) {
|
||||
.i64 => return .i64_store32,
|
||||
.i32, .f32, .f64 => unreachable,
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
} else {
|
||||
switch (args.valtype1.?) {
|
||||
.i32 => return .i32_store,
|
||||
.i64 => return .i64_store,
|
||||
.f32 => return .f32_store,
|
||||
.f64 => return .f64_store,
|
||||
}
|
||||
},
|
||||
|
||||
.memory_size => return .memory_size,
|
||||
.memory_grow => return .memory_grow,
|
||||
|
||||
.@"const" => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_const,
|
||||
.i64 => return .i64_const,
|
||||
.f32 => return .f32_const,
|
||||
.f64 => return .f64_const,
|
||||
},
|
||||
|
||||
.eqz => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_eqz,
|
||||
.i64 => return .i64_eqz,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.eq => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_eq,
|
||||
.i64 => return .i64_eq,
|
||||
.f32 => return .f32_eq,
|
||||
.f64 => return .f64_eq,
|
||||
},
|
||||
.ne => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_ne,
|
||||
.i64 => return .i64_ne,
|
||||
.f32 => return .f32_ne,
|
||||
.f64 => return .f64_ne,
|
||||
},
|
||||
|
||||
.lt => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_lt_s else return .i32_lt_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_lt_s else return .i64_lt_u,
|
||||
.f32 => return .f32_lt,
|
||||
.f64 => return .f64_lt,
|
||||
},
|
||||
.gt => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_gt_s else return .i32_gt_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_gt_s else return .i64_gt_u,
|
||||
.f32 => return .f32_gt,
|
||||
.f64 => return .f64_gt,
|
||||
},
|
||||
.le => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_le_s else return .i32_le_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_le_s else return .i64_le_u,
|
||||
.f32 => return .f32_le,
|
||||
.f64 => return .f64_le,
|
||||
},
|
||||
.ge => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_ge_s else return .i32_ge_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_ge_s else return .i64_ge_u,
|
||||
.f32 => return .f32_ge,
|
||||
.f64 => return .f64_ge,
|
||||
},
|
||||
|
||||
.clz => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_clz,
|
||||
.i64 => return .i64_clz,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.ctz => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_ctz,
|
||||
.i64 => return .i64_ctz,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.popcnt => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_popcnt,
|
||||
.i64 => return .i64_popcnt,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
|
||||
.add => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_add,
|
||||
.i64 => return .i64_add,
|
||||
.f32 => return .f32_add,
|
||||
.f64 => return .f64_add,
|
||||
},
|
||||
.sub => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_sub,
|
||||
.i64 => return .i64_sub,
|
||||
.f32 => return .f32_sub,
|
||||
.f64 => return .f64_sub,
|
||||
},
|
||||
.mul => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_mul,
|
||||
.i64 => return .i64_mul,
|
||||
.f32 => return .f32_mul,
|
||||
.f64 => return .f64_mul,
|
||||
},
|
||||
|
||||
.div => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_div_s else return .i32_div_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_div_s else return .i64_div_u,
|
||||
.f32 => return .f32_div,
|
||||
.f64 => return .f64_div,
|
||||
},
|
||||
.rem => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_rem_s else return .i32_rem_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_rem_s else return .i64_rem_u,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
|
||||
.@"and" => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_and,
|
||||
.i64 => return .i64_and,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.@"or" => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_or,
|
||||
.i64 => return .i64_or,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.xor => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_xor,
|
||||
.i64 => return .i64_xor,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
|
||||
.shl => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_shl,
|
||||
.i64 => return .i64_shl,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.shr => switch (args.valtype1.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .i32_shr_s else return .i32_shr_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .i64_shr_s else return .i64_shr_u,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.rotl => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_rotl,
|
||||
.i64 => return .i64_rotl,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.rotr => switch (args.valtype1.?) {
|
||||
.i32 => return .i32_rotr,
|
||||
.i64 => return .i64_rotr,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
|
||||
.abs => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_abs,
|
||||
.f64 => return .f64_abs,
|
||||
},
|
||||
.neg => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_neg,
|
||||
.f64 => return .f64_neg,
|
||||
},
|
||||
.ceil => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_ceil,
|
||||
.f64 => return .f64_ceil,
|
||||
},
|
||||
.floor => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_floor,
|
||||
.f64 => return .f64_floor,
|
||||
},
|
||||
.trunc => switch (args.valtype1.?) {
|
||||
.i32 => switch (args.valtype2.?) {
|
||||
.i32 => unreachable,
|
||||
.i64 => unreachable,
|
||||
.f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
|
||||
.f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
|
||||
},
|
||||
.i64 => unreachable,
|
||||
.f32 => return .f32_trunc,
|
||||
.f64 => return .f64_trunc,
|
||||
},
|
||||
.nearest => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_nearest,
|
||||
.f64 => return .f64_nearest,
|
||||
},
|
||||
.sqrt => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_sqrt,
|
||||
.f64 => return .f64_sqrt,
|
||||
},
|
||||
.min => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_min,
|
||||
.f64 => return .f64_min,
|
||||
},
|
||||
.max => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_max,
|
||||
.f64 => return .f64_max,
|
||||
},
|
||||
.copysign => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => return .f32_copysign,
|
||||
.f64 => return .f64_copysign,
|
||||
},
|
||||
|
||||
.wrap => switch (args.valtype1.?) {
|
||||
.i32 => switch (args.valtype2.?) {
|
||||
.i32 => unreachable,
|
||||
.i64 => return .i32_wrap_i64,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.i64, .f32, .f64 => unreachable,
|
||||
},
|
||||
.convert => switch (args.valtype1.?) {
|
||||
.i32, .i64 => unreachable,
|
||||
.f32 => switch (args.valtype2.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .f32_convert_i32_s else return .f32_convert_i32_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .f32_convert_i64_s else return .f32_convert_i64_u,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
.f64 => switch (args.valtype2.?) {
|
||||
.i32 => if (args.signedness.? == .signed) return .f64_convert_i32_s else return .f64_convert_i32_u,
|
||||
.i64 => if (args.signedness.? == .signed) return .f64_convert_i64_s else return .f64_convert_i64_u,
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
},
|
||||
.demote => if (args.valtype1.? == .f32 and args.valtype2.? == .f64) return .f32_demote_f64 else unreachable,
|
||||
.promote => if (args.valtype1.? == .f64 and args.valtype2.? == .f32) return .f64_promote_f32 else unreachable,
|
||||
.reinterpret => switch (args.valtype1.?) {
|
||||
.i32 => if (args.valtype2.? == .f32) return .i32_reinterpret_f32 else unreachable,
|
||||
.i64 => if (args.valtype2.? == .f64) return .i64_reinterpret_f64 else unreachable,
|
||||
.f32 => if (args.valtype2.? == .i32) return .f32_reinterpret_i32 else unreachable,
|
||||
.f64 => if (args.valtype2.? == .i64) return .f64_reinterpret_i64 else unreachable,
|
||||
},
|
||||
.extend => switch (args.valtype1.?) {
|
||||
.i32 => switch (args.width.?) {
|
||||
8 => if (args.signedness.? == .signed) return .i32_extend8_s else unreachable,
|
||||
16 => if (args.signedness.? == .signed) return .i32_extend16_s else unreachable,
|
||||
else => unreachable,
|
||||
},
|
||||
.i64 => switch (args.width.?) {
|
||||
8 => if (args.signedness.? == .signed) return .i64_extend8_s else unreachable,
|
||||
16 => if (args.signedness.? == .signed) return .i64_extend16_s else unreachable,
|
||||
32 => if (args.signedness.? == .signed) return .i64_extend32_s else unreachable,
|
||||
else => unreachable,
|
||||
},
|
||||
.f32, .f64 => unreachable,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test "Wasm - buildOpcode" {
|
||||
// Make sure buildOpcode is referenced, and test some examples
|
||||
const i32_const = buildOpcode(.{ .op = .@"const", .valtype1 = .i32 });
|
||||
const end = buildOpcode(.{ .op = .end });
|
||||
const local_get = buildOpcode(.{ .op = .local_get });
|
||||
const i64_extend32_s = buildOpcode(.{ .op = .extend, .valtype1 = .i64, .width = 32, .signedness = .signed });
|
||||
const f64_reinterpret_i64 = buildOpcode(.{ .op = .reinterpret, .valtype1 = .f64, .valtype2 = .i64 });
|
||||
|
||||
testing.expectEqual(@as(wasm.Opcode, .i32_const), i32_const);
|
||||
testing.expectEqual(@as(wasm.Opcode, .end), end);
|
||||
testing.expectEqual(@as(wasm.Opcode, .local_get), local_get);
|
||||
testing.expectEqual(@as(wasm.Opcode, .i64_extend32_s), i64_extend32_s);
|
||||
testing.expectEqual(@as(wasm.Opcode, .f64_reinterpret_i64), f64_reinterpret_i64);
|
||||
}
|
||||
|
||||
pub const Result = union(enum) {
|
||||
/// The codegen bytes have been appended to `Context.code`
|
||||
appended: void,
|
||||
/// The data is managed externally and are part of the `Result`
|
||||
externally_managed: []const u8,
|
||||
};
|
||||
|
||||
/// Hashmap to store generated `WValue` for each `Inst`
|
||||
pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
|
||||
|
||||
@@ -58,10 +506,14 @@ pub const Context = struct {
|
||||
/// List of all locals' types generated throughout this declaration
|
||||
/// used to emit locals count at start of 'code' section.
|
||||
locals: std.ArrayListUnmanaged(u8),
|
||||
/// The Target we're emitting (used to call intInfo)
|
||||
target: std.Target,
|
||||
|
||||
const InnerError = error{
|
||||
OutOfMemory,
|
||||
CodegenFail,
|
||||
/// Can occur when dereferencing a pointer that points to a `Decl` of which the analysis has failed
|
||||
AnalysisFail,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
@@ -89,17 +541,31 @@ pub const Context = struct {
|
||||
return self.values.get(inst).?; // Instruction does not dominate all uses!
|
||||
}
|
||||
|
||||
/// Using a given `Type`, returns the corresponding wasm value type
|
||||
fn genValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
|
||||
return switch (ty.tag()) {
|
||||
.f32 => wasm.valtype(.f32),
|
||||
.f64 => wasm.valtype(.f64),
|
||||
.u32, .i32, .bool => wasm.valtype(.i32),
|
||||
.u64, .i64 => wasm.valtype(.i64),
|
||||
else => self.fail(src, "TODO - Wasm genValtype for type '{s}'", .{ty.tag()}),
|
||||
/// Using a given `Type`, returns the corresponding wasm Valtype
|
||||
fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {
|
||||
return switch (ty.zigTypeTag()) {
|
||||
.Float => blk: {
|
||||
const bits = ty.floatBits(self.target);
|
||||
if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
|
||||
if (bits == 64) break :blk wasm.Valtype.f64;
|
||||
return self.fail(src, "Float bit size not supported by wasm: '{d}'", .{bits});
|
||||
},
|
||||
.Int => blk: {
|
||||
const info = ty.intInfo(self.target);
|
||||
if (info.bits <= 32) break :blk wasm.Valtype.i32;
|
||||
if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
|
||||
return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
|
||||
},
|
||||
.Bool, .Pointer => wasm.Valtype.i32,
|
||||
else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.tag()}),
|
||||
};
|
||||
}
|
||||
|
||||
/// Using a given `Type`, returns the byte representation of its wasm value type
|
||||
fn genValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
|
||||
return wasm.valtype(try self.typeToValtype(src, ty));
|
||||
}
|
||||
|
||||
/// Using a given `Type`, returns the corresponding wasm value type
|
||||
/// Differently from `genValtype` this also allows `void` to create a block
|
||||
/// with no return type
|
||||
@@ -157,59 +623,97 @@ pub const Context = struct {
|
||||
}
|
||||
|
||||
/// Generates the wasm bytecode for the function declaration belonging to `Context`
|
||||
pub fn gen(self: *Context) InnerError!void {
|
||||
assert(self.code.items.len == 0);
|
||||
try self.genFunctype();
|
||||
pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result {
|
||||
switch (typed_value.ty.zigTypeTag()) {
|
||||
.Fn => {
|
||||
try self.genFunctype();
|
||||
|
||||
// Write instructions
|
||||
// TODO: check for and handle death of instructions
|
||||
const tv = self.decl.typed_value.most_recent.typed_value;
|
||||
const mod_fn = blk: {
|
||||
if (tv.val.castTag(.function)) |func| break :blk func.data;
|
||||
if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions
|
||||
return self.fail(.{ .node_offset = 0 }, "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()});
|
||||
};
|
||||
// Write instructions
|
||||
// TODO: check for and handle death of instructions
|
||||
const mod_fn = blk: {
|
||||
if (typed_value.val.castTag(.function)) |func| break :blk func.data;
|
||||
if (typed_value.val.castTag(.extern_fn)) |ext_fn| return Result.appended; // don't need code body for extern functions
|
||||
unreachable;
|
||||
};
|
||||
|
||||
// Reserve space to write the size after generating the code as well as space for locals count
|
||||
try self.code.resize(10);
|
||||
// Reserve space to write the size after generating the code as well as space for locals count
|
||||
try self.code.resize(10);
|
||||
|
||||
try self.genBody(mod_fn.body);
|
||||
try self.genBody(mod_fn.body);
|
||||
|
||||
// finally, write our local types at the 'offset' position
|
||||
{
|
||||
leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
|
||||
// finally, write our local types at the 'offset' position
|
||||
{
|
||||
leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
|
||||
|
||||
// offset into 'code' section where we will put our locals types
|
||||
var local_offset: usize = 10;
|
||||
// offset into 'code' section where we will put our locals types
|
||||
var local_offset: usize = 10;
|
||||
|
||||
// emit the actual locals amount
|
||||
for (self.locals.items) |local| {
|
||||
var buf: [6]u8 = undefined;
|
||||
leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
|
||||
buf[5] = local;
|
||||
try self.code.insertSlice(local_offset, &buf);
|
||||
local_offset += 6;
|
||||
}
|
||||
// emit the actual locals amount
|
||||
for (self.locals.items) |local| {
|
||||
var buf: [6]u8 = undefined;
|
||||
leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
|
||||
buf[5] = local;
|
||||
try self.code.insertSlice(local_offset, &buf);
|
||||
local_offset += 6;
|
||||
}
|
||||
}
|
||||
|
||||
const writer = self.code.writer();
|
||||
try writer.writeByte(wasm.opcode(.end));
|
||||
|
||||
// Fill in the size of the generated code to the reserved space at the
|
||||
// beginning of the buffer.
|
||||
const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5;
|
||||
leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
|
||||
|
||||
// codegen data has been appended to `code`
|
||||
return Result.appended;
|
||||
},
|
||||
.Array => {
|
||||
if (typed_value.val.castTag(.bytes)) |payload| {
|
||||
if (typed_value.ty.sentinel()) |sentinel| {
|
||||
try self.code.appendSlice(payload.data);
|
||||
|
||||
switch (try self.gen(.{
|
||||
.ty = typed_value.ty.elemType(),
|
||||
.val = sentinel,
|
||||
})) {
|
||||
.appended => return Result.appended,
|
||||
.externally_managed => |data| {
|
||||
try self.code.appendSlice(data);
|
||||
return Result.appended;
|
||||
},
|
||||
}
|
||||
}
|
||||
return Result{ .externally_managed = payload.data };
|
||||
} else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
|
||||
},
|
||||
.Int => {
|
||||
const info = typed_value.ty.intInfo(self.target);
|
||||
if (info.bits == 8 and info.signedness == .unsigned) {
|
||||
const int_byte = typed_value.val.toUnsignedInt();
|
||||
try self.code.append(@intCast(u8, int_byte));
|
||||
return Result.appended;
|
||||
}
|
||||
return self.fail(.{ .node_offset = 0 }, "TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
|
||||
},
|
||||
else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}),
|
||||
}
|
||||
|
||||
const writer = self.code.writer();
|
||||
try writer.writeByte(wasm.opcode(.end));
|
||||
|
||||
// Fill in the size of the generated code to the reserved space at the
|
||||
// beginning of the buffer.
|
||||
const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5;
|
||||
leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
|
||||
}
|
||||
|
||||
fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
|
||||
return switch (inst.tag) {
|
||||
.add => self.genAdd(inst.castTag(.add).?),
|
||||
.add => self.genBinOp(inst.castTag(.add).?, .add),
|
||||
.alloc => self.genAlloc(inst.castTag(.alloc).?),
|
||||
.arg => self.genArg(inst.castTag(.arg).?),
|
||||
.block => self.genBlock(inst.castTag(.block).?),
|
||||
.breakpoint => self.genBreakpoint(inst.castTag(.breakpoint).?),
|
||||
.br => self.genBr(inst.castTag(.br).?),
|
||||
.call => self.genCall(inst.castTag(.call).?),
|
||||
.bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"),
|
||||
.bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"),
|
||||
.bool_or => self.genBinOp(inst.castTag(.bool_or).?, .@"or"),
|
||||
.bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"),
|
||||
.cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq),
|
||||
.cmp_gte => self.genCmp(inst.castTag(.cmp_gte).?, .gte),
|
||||
.cmp_gt => self.genCmp(inst.castTag(.cmp_gt).?, .gt),
|
||||
@@ -221,10 +725,14 @@ pub const Context = struct {
|
||||
.dbg_stmt => WValue.none,
|
||||
.load => self.genLoad(inst.castTag(.load).?),
|
||||
.loop => self.genLoop(inst.castTag(.loop).?),
|
||||
.mul => self.genBinOp(inst.castTag(.mul).?, .mul),
|
||||
.div => self.genBinOp(inst.castTag(.div).?, .div),
|
||||
.xor => self.genBinOp(inst.castTag(.xor).?, .xor),
|
||||
.not => self.genNot(inst.castTag(.not).?),
|
||||
.ret => self.genRet(inst.castTag(.ret).?),
|
||||
.retvoid => WValue.none,
|
||||
.store => self.genStore(inst.castTag(.store).?),
|
||||
.sub => self.genBinOp(inst.castTag(.sub).?, .sub),
|
||||
.unreach => self.genUnreachable(inst.castTag(.unreach).?),
|
||||
else => self.fail(inst.src, "TODO: Implement wasm inst: {s}", .{inst.tag}),
|
||||
};
|
||||
@@ -266,7 +774,7 @@ pub const Context = struct {
|
||||
|
||||
// The function index immediate argument will be filled in using this data
|
||||
// in link.Wasm.flush().
|
||||
try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{
|
||||
try self.decl.fn_link.wasm.idx_refs.append(self.gpa, .{
|
||||
.offset = @intCast(u32, self.code.items.len),
|
||||
.decl = target,
|
||||
});
|
||||
@@ -305,56 +813,76 @@ pub const Context = struct {
|
||||
return WValue{ .local = self.local_index };
|
||||
}
|
||||
|
||||
fn genAdd(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
|
||||
fn genBinOp(self: *Context, inst: *Inst.BinOp, op: Op) InnerError!WValue {
|
||||
const lhs = self.resolveInst(inst.lhs);
|
||||
const rhs = self.resolveInst(inst.rhs);
|
||||
|
||||
try self.emitWValue(lhs);
|
||||
try self.emitWValue(rhs);
|
||||
|
||||
const opcode: wasm.Opcode = switch (inst.base.ty.tag()) {
|
||||
.u32, .i32 => .i32_add,
|
||||
.u64, .i64 => .i64_add,
|
||||
.f32 => .f32_add,
|
||||
.f64 => .f64_add,
|
||||
else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}),
|
||||
};
|
||||
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.op = op,
|
||||
.valtype1 = try self.typeToValtype(inst.base.src, inst.base.ty),
|
||||
.signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned,
|
||||
});
|
||||
try self.code.append(wasm.opcode(opcode));
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void {
|
||||
const writer = self.code.writer();
|
||||
switch (inst.base.ty.tag()) {
|
||||
.u32 => {
|
||||
try writer.writeByte(wasm.opcode(.i32_const));
|
||||
try leb.writeILEB128(writer, inst.val.toUnsignedInt());
|
||||
switch (inst.base.ty.zigTypeTag()) {
|
||||
.Int => {
|
||||
// write opcode
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.op = .@"const",
|
||||
.valtype1 = try self.typeToValtype(inst.base.src, inst.base.ty),
|
||||
});
|
||||
try writer.writeByte(wasm.opcode(opcode));
|
||||
// write constant
|
||||
switch (inst.base.ty.intInfo(self.target).signedness) {
|
||||
.signed => try leb.writeILEB128(writer, inst.val.toSignedInt()),
|
||||
.unsigned => try leb.writeILEB128(writer, inst.val.toUnsignedInt()),
|
||||
}
|
||||
},
|
||||
.i32, .bool => {
|
||||
.Bool => {
|
||||
// write opcode
|
||||
try writer.writeByte(wasm.opcode(.i32_const));
|
||||
// write constant
|
||||
try leb.writeILEB128(writer, inst.val.toSignedInt());
|
||||
},
|
||||
.u64 => {
|
||||
try writer.writeByte(wasm.opcode(.i64_const));
|
||||
try leb.writeILEB128(writer, inst.val.toUnsignedInt());
|
||||
.Float => {
|
||||
// write opcode
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.op = .@"const",
|
||||
.valtype1 = try self.typeToValtype(inst.base.src, inst.base.ty),
|
||||
});
|
||||
try writer.writeByte(wasm.opcode(opcode));
|
||||
// write constant
|
||||
switch (inst.base.ty.floatBits(self.target)) {
|
||||
0...32 => try writer.writeIntLittle(u32, @bitCast(u32, inst.val.toFloat(f32))),
|
||||
64 => try writer.writeIntLittle(u64, @bitCast(u64, inst.val.toFloat(f64))),
|
||||
else => |bits| return self.fail(inst.base.src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),
|
||||
}
|
||||
},
|
||||
.i64 => {
|
||||
try writer.writeByte(wasm.opcode(.i64_const));
|
||||
try leb.writeILEB128(writer, inst.val.toSignedInt());
|
||||
.Pointer => {
|
||||
if (inst.val.castTag(.decl_ref)) |payload| {
|
||||
const decl = payload.data;
|
||||
|
||||
// offset into the offset table within the 'data' section
|
||||
const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
|
||||
try writer.writeByte(wasm.opcode(.i32_const));
|
||||
try leb.writeULEB128(writer, decl.link.wasm.offset_index * ptr_width);
|
||||
|
||||
// memory instruction followed by their memarg immediate
|
||||
// memarg ::== x:u32, y:u32 => {align x, offset y}
|
||||
try writer.writeByte(wasm.opcode(.i32_load));
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
} else return self.fail(inst.base.src, "Wasm TODO: emitConstant for other const pointer tag {s}", .{inst.val.tag()});
|
||||
},
|
||||
.f32 => {
|
||||
try writer.writeByte(wasm.opcode(.f32_const));
|
||||
// TODO: enforce LE byte order
|
||||
try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
|
||||
},
|
||||
.f64 => {
|
||||
try writer.writeByte(wasm.opcode(.f64_const));
|
||||
// TODO: enforce LE byte order
|
||||
try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
|
||||
},
|
||||
.void => {},
|
||||
else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}),
|
||||
.Void => {},
|
||||
else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{ty}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,62 +983,18 @@ pub const Context = struct {
|
||||
try self.emitWValue(lhs);
|
||||
try self.emitWValue(rhs);
|
||||
|
||||
const opcode_maybe: ?wasm.Opcode = switch (op) {
|
||||
.lt => @as(?wasm.Opcode, switch (ty) {
|
||||
.i32 => .i32_lt_s,
|
||||
.u32 => .i32_lt_u,
|
||||
.i64 => .i64_lt_s,
|
||||
.u64 => .i64_lt_u,
|
||||
.f32 => .f32_lt,
|
||||
.f64 => .f64_lt,
|
||||
else => null,
|
||||
}),
|
||||
.lte => @as(?wasm.Opcode, switch (ty) {
|
||||
.i32 => .i32_le_s,
|
||||
.u32 => .i32_le_u,
|
||||
.i64 => .i64_le_s,
|
||||
.u64 => .i64_le_u,
|
||||
.f32 => .f32_le,
|
||||
.f64 => .f64_le,
|
||||
else => null,
|
||||
}),
|
||||
.eq => @as(?wasm.Opcode, switch (ty) {
|
||||
.i32, .u32 => .i32_eq,
|
||||
.i64, .u64 => .i64_eq,
|
||||
.f32 => .f32_eq,
|
||||
.f64 => .f64_eq,
|
||||
else => null,
|
||||
}),
|
||||
.gte => @as(?wasm.Opcode, switch (ty) {
|
||||
.i32 => .i32_ge_s,
|
||||
.u32 => .i32_ge_u,
|
||||
.i64 => .i64_ge_s,
|
||||
.u64 => .i64_ge_u,
|
||||
.f32 => .f32_ge,
|
||||
.f64 => .f64_ge,
|
||||
else => null,
|
||||
}),
|
||||
.gt => @as(?wasm.Opcode, switch (ty) {
|
||||
.i32 => .i32_gt_s,
|
||||
.u32 => .i32_gt_u,
|
||||
.i64 => .i64_gt_s,
|
||||
.u64 => .i64_gt_u,
|
||||
.f32 => .f32_gt,
|
||||
.f64 => .f64_gt,
|
||||
else => null,
|
||||
}),
|
||||
.neq => @as(?wasm.Opcode, switch (ty) {
|
||||
.i32, .u32 => .i32_ne,
|
||||
.i64, .u64 => .i64_ne,
|
||||
.f32 => .f32_ne,
|
||||
.f64 => .f64_ne,
|
||||
else => null,
|
||||
}),
|
||||
};
|
||||
|
||||
const opcode = opcode_maybe orelse
|
||||
return self.fail(inst.base.src, "TODO - Wasm genCmp for type '{s}' and operator '{s}'", .{ ty, @tagName(op) });
|
||||
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.valtype1 = try self.typeToValtype(inst.base.src, inst.lhs.ty),
|
||||
.op = switch (op) {
|
||||
.lt => .lt,
|
||||
.lte => .le,
|
||||
.eq => .eq,
|
||||
.neq => .ne,
|
||||
.gte => .ge,
|
||||
.gt => .gt,
|
||||
},
|
||||
.signedness = inst.lhs.ty.intInfo(self.target).signedness,
|
||||
});
|
||||
try self.code.append(wasm.opcode(opcode));
|
||||
return WValue{ .code_offset = offset };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user