//! .zov.ve-sistema:x86-64 //! //! Provides entry opcodes for System V calling convention, optimized for specific prototypes. //! // https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf // todo: Provide opcode that would dynamically dispatch based on marshaled C prototypes, // which will be sufficient for rare prototypes sporadically used, so to not bloat // the binary with all possible permutations or ask for them on comptime, which is unreasonable. const std = @import("std"); const tolmac = @import("../../../tolmac.zig"); /// Used for stack parameter passing. pub const WordLimit = 128; const AsmBufferLimit = 4096; const ClassBufferLimit = 256; const Class = enum { void, // Denotes empty types. integer, sse, sseup, x87, x87up, no_class, memory, }; fn determiteClass(comptime T: type, buffer: []Class) []Class { switch (@typeInfo(T)) { .Void => &[1]Class{.void}, .Int => |int| { switch (int.bits) { 0 => buffer[0] = .void, 1...64 => buffer[0] = .integer, 65...128 => @compileError("unimplemented"), else => @compileError("invalid sysv parameter"), } }, .Float => |float| { switch (float.bits) { 0 => buffer[0] = .void, 1...64 => buffer[0] = .sse, 65...80 => @compileError("unimplemented"), 81...128 => @compileError("unimplemented"), else => @compileError("invalid sysv parameter"), } }, .Bool => buffer[0] = .integer, .Pointer => |ptr| { switch (ptr.size) { .Slice => @compileError("invalid sysv parameter"), else => buffer[0] = .integer, } }, .Fn => buffer[0] = .integer, else => @compileError("unimplemented"), } return buffer[0 .. (@sizeOf(T) - 1) / 8 + 1]; } // todo: Make sure duplicates are not made. // todo: Cache results for identical in effect devices. // /// (iw | -- (arbitrary amount of words)) pub fn generateOpZovSysvFromPrototype(prototype: anytype) !*const fn () callconv(.Naked) noreturn { // todo: Should we care about this? // > The direction flag DF in the %rFLAGS register must be clear (set to “forward” // > direction) on function entry and return. // todo: Is our bool convention compatible? // > Booleans, when stored in a memory object, are stored as single byte objects the // > value of which is always 0 (false) or 1 (true). When stored in integer registers // > (except for passing as arguments), all 8 bytes of the register are significant; any // > nonzero value is considered true. comptime { const func = @typeInfo(@TypeOf(prototype)).Fn; if (func.calling_convention != .SysV) @compileError("Non SysV function passed"); var source_buffer = [_]u8{0} ** AsmBufferLimit; var source_needle: usize = 0; // idea: Try using REP for big consequent memory pushes. // todo: In-stack returns by pointing %rdi directly to final destination. // todo: Handle cases with more than 32 eightbytes on stack. // For that we would need to increment %rbp by 256 every once in a while, // either by storing its copy in volatile register or subtracting back after copy it done. // todo: Test whether aligning by shifting is better. const Prelude = \\ movq %%rsp, %%rbp # Move stack pointer in non-volatile %rbp to restore later \\ andq $-16, %%rsp # Align stack so that %rsp + 8 in callee is 16 aligned. \\ subq $0x{x}, %%rsp \\ ; const Call = \\ call *8(%%r12) \\ ; const Epilogue = \\ movq %%rbp, %%rsp # Restore stack pointer \\ addq $0x10, %%r12 \\ jmpq *(%%r12) \\ ; var integer_allocation: usize = 0; const IntegerAllocations = [_][]const u8{ "rdi", "rsi", "rdx", "rcx", "r8", "r9" }; // var sse_allocation: enum { xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 } = .xmm0; var class_buffer = [_]Class{.void} ** ClassBufferLimit; // Calculate stack space used by parameters. var parameter_stack_size: usize = 0; for (func.params) |param| { const classes = determiteClass(param.type.?, &class_buffer); parameter_stack_size += 8 * classes.len; } source_needle += (try std.fmt.bufPrint( source_buffer[source_needle..], Prelude[0..], .{if (parameter_stack_size % 16 == 0) 16 else 8}, )).len; // Push parameters to appropriate registers and stack positions. var stack_offset: usize = parameter_stack_size; for (func.params) |param| { const classes = determiteClass(param.type.?, &class_buffer); for (classes) |class| { stack_offset -= 8; switch (class) { .integer => { if (integer_allocation < IntegerAllocations.len - 1) { source_needle += (try std.fmt.bufPrint( source_buffer[source_needle..], "movq {}(%%rbp), %%{s}\n", .{ stack_offset, IntegerAllocations[integer_allocation] }, )).len; integer_allocation += 1; } else { source_needle += (try std.fmt.bufPrint( source_buffer[source_needle..], "pushq {}(%%rbp)\n", .{stack_offset}, )).len; } }, .void => {}, else => @compileError("unimplemented"), } } } @memcpy(source_buffer[source_needle .. source_needle + Call.len], Call[0..]); source_needle += Call.len; @memcpy(source_buffer[source_needle .. source_needle + Epilogue.len], Epilogue[0..]); source_needle += Epilogue.len; return &struct { fn op() callconv(.Naked) noreturn { asm volatile (source_buffer[0..source_needle]); } }.op; } } fn addInt1(a: u64) callconv(.SysV) void { @setAlignStack(16); if (a != 1) @panic("addInt1"); } fn addInt2(a: u64, b: u8) callconv(.SysV) void { @setAlignStack(16); if ((a + b) != 3) @panic("addInt2"); } fn addInt3(a: u64, b: u32, c: u16) callconv(.SysV) void { @setAlignStack(16); if ((a + b + c) != 6) @panic("addInt3"); } fn addInt4(a: u64, b: u32, c: u16, d: u8) callconv(.SysV) void { @setAlignStack(16); if ((a + b + c + d) != 10) @panic("addInt4"); } fn addInt5(a: u64, b: u32, c: u16, d: u8, e: u64) callconv(.SysV) void { @setAlignStack(16); if ((a + b + c + d + e) != 11) @panic("addInt5"); } fn addInt6(a: u64, b: u32, c: u16, d: u8, e: u64, f: i32) callconv(.SysV) void { @setAlignStack(16); if ((a + b + c + d + e + @as(u64, @intCast(f))) != 15) @panic("addInt6"); } fn addInt7(a: u64, b: u32, c: u16, d: u8, e: u64, f: i32, sa: u32) callconv(.SysV) void { @setAlignStack(16); if ((a + b + c + d + e + @as(u64, @intCast(f)) + sa) != 30) @panic("addInt7"); } fn addInt8(a: u64, b: u32, c: u16, d: u8, e: u64, f: i32, sa: u32, sb: u8) callconv(.SysV) void { @setAlignStack(16); if ((a + b + c + d + e + @as(u64, @intCast(f)) + sa + sb) != 40) @panic("addInt8"); } const opZov1Int = generateOpZovSysvFromPrototype(addInt1) catch unreachable; const opZov2Ints = generateOpZovSysvFromPrototype(addInt2) catch unreachable; const opZov3Ints = generateOpZovSysvFromPrototype(addInt3) catch unreachable; const opZov4Ints = generateOpZovSysvFromPrototype(addInt4) catch unreachable; const opZov5Ints = generateOpZovSysvFromPrototype(addInt5) catch unreachable; const opZov6Ints = generateOpZovSysvFromPrototype(addInt6) catch unreachable; const opZov7Ints = generateOpZovSysvFromPrototype(addInt7) catch unreachable; const opZov8Ints = generateOpZovSysvFromPrototype(addInt8) catch unreachable; test "integer parameter passing" { const code = [_]tolmac.Word{ @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 1, @as(tolmac.Word, @intFromPtr(opZov1Int)), @as(tolmac.Word, @intFromPtr(&addInt1)), @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 2, @as(tolmac.Word, @intFromPtr(opZov2Ints)), @as(tolmac.Word, @intFromPtr(&addInt2)), @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 3, @as(tolmac.Word, @intFromPtr(opZov3Ints)), @as(tolmac.Word, @intFromPtr(&addInt3)), @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 4, @as(tolmac.Word, @intFromPtr(opZov4Ints)), @as(tolmac.Word, @intFromPtr(&addInt4)), @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 1, @as(tolmac.Word, @intFromPtr(opZov5Ints)), @as(tolmac.Word, @intFromPtr(&addInt5)), @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 4, @as(tolmac.Word, @intFromPtr(opZov6Ints)), @as(tolmac.Word, @intFromPtr(&addInt6)), @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 15, @as(tolmac.Word, @intFromPtr(opZov7Ints)), @as(tolmac.Word, @intFromPtr(&addInt7)), @as(tolmac.Word, @intFromPtr(&tolmac.opPushWord)), 10, @as(tolmac.Word, @intFromPtr(opZov8Ints)), @as(tolmac.Word, @intFromPtr(&addInt8)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opSinkWord)), @as(tolmac.Word, @intFromPtr(&tolmac.opReturn)), }; tolmac.execute(&code, 0); }