#Am I calling my jitted code wrong?

1 messages · Page 1 of 1 (latest)

thorn vapor
#

So I am following a guide on how to do a simple JIT from here (https://www.youtube.com/watch?v=8mxubNQC5O8)
In his code he does

void run(VM& vm) {
  typedef void (*JittedFunction)(VM&, Value* registers, Value* locals);
  (*(JittedFunction)code)(vm, vm.registers.data(), vm.locals.data());
}```

and in mine I try to 

```rs
pub fn run(self: *@This(), vm: *VM) !void {
  // RDI: VM&
  // RSI: Value* registers
  // RDX: Value* locals
  const func: *const fn (vm: *VM, regs: [*]Value, locals: [*]Value) void = @alignCast(@ptrCast(self.code));
  @call(.auto, func, .{ vm, vm.registers.items.ptr, vm.locals.items.ptr });
}```

Im wondering if I am calling my jit wrong, or my assembly is wrong.
My entire code is available here: https://zigbin.io/3dd803
Exit error:
```rs
Segmentation fault at address 0x0
???:?:?: 0x7aff72b53067 in ??? (???)
Unwind information for `???:0x7aff72b53067` was not available, trace may be incomplete

Assembly (nbdisasm -b64 of the code): https://zigbin.io/fd4b6c

pliant herald
#

you probably need to mark the memory as executable using something like mmap and mprotect

thorn vapor
#
        const executable_memory = try std.os.mmap(null, self.assembler.output.items.len, std.os.PROT.READ | std.os.PROT.WRITE | std.os.PROT.EXEC, std.os.MAP.ANONYMOUS | std.os.MAP.PRIVATE, 0, 0);
        @memcpy(executable_memory, self.assembler.output.items);
#

i think im already doing it

#

important to note that run is called from this struct, in the function where I compile im sure I return the correct struct

const Executable = struct {
    code: []u8,
    code_size: usize
};
dusk roost
#

depending on your system it may not be legal to map a page as RWX

thorn vapor
#

I am using arch linux (btw)

dusk roost
#

write and execute are generally exclusive

thorn vapor
#

and x86-64

#

I can try to

#

so first read write

#

and then mprotect to read and exec

#

?

#
thread 51834 panic: reached unreachable code
/home/tiramify/.zigd/versions/0.12.0-dev.2334+aef1da163/lib/std/debug.zig:403:14: 0x102f892 in assert (jit-vm)
    if (!ok) unreachable; // assertion failure
             ^
/home/tiramify/.zigd/versions/0.12.0-dev.2334+aef1da163/lib/std/os.zig:4592:11: 0x102b84a in mprotect (jit-vm)
    assert(mem.isAligned(memory.len, mem.page_size));
          ^
/home/tiramify/Documents/Programming/zig/jit-vm/src/main.zig:464:28: 0x102aefb in compile (jit-vm)
        try std.os.mprotect(executable_memory, std.os.PROT.READ | std.os.PROT.EXEC);
#

it's not aligned

#

what does it mean?

#

(the code now)

        const executable_memory = try std.os.mmap(null, self.assembler.output.items.len, std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.ANONYMOUS | std.os.MAP.PRIVATE, 0, 0);
        @memcpy(executable_memory, self.assembler.output.items);

        try std.os.mprotect(executable_memory, std.os.PROT.READ | std.os.PROT.EXEC);
dusk roost
#

mprotect operates on pages, you must give it pages

thorn vapor
#

like...?

dusk roost
#

it doesn't make sense to mprotect a random slice, the address must be page aligned and the size must be a multiple of the page size

thorn vapor
#

hmmm

#

what do I do

dusk roost
#

mmap returns a slice of the length you request

#

either request a page aligned length, or use executable_memory to create a slice of the actual allocated pages

thorn vapor
#

so I need to request std.mem.page_size?

dusk roost
#

you need to request std.mem.page_size aligned self.assembler.output.items.len

thorn vapor
#
        const executable_memory = try std.os.mmap(null, std.mem.page_size, std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.ANONYMOUS | std.os.MAP.PRIVATE, 0, 0);
        @memcpy(executable_memory[0..self.assembler.output.items.len], self.assembler.output.items);

        try std.os.mprotect(executable_memory, std.os.PROT.READ | std.os.PROT.EXEC);
#

like this?

dusk roost
thorn vapor
#

okay

dusk roost
#

I'd recommend alignForward

thorn vapor
#

uh

#

how do I use it

dusk roost
#

it's a function in std.mem, you call it

thorn vapor
#

yaeh but

#

first argument is a type

#

usize ig?

dusk roost
#

usize is a reasonable type for an address

thorn vapor
#

std.mem.alignForward(usize, self.assembler.output.items.len, std.mem.page_size);

#

?

dusk roost
#

yes

thorn vapor
#

okay

#

so I did

        const executable_memory = try std.os.mmap(null, std.mem.alignForward(usize, self.assembler.output.items.len, std.mem.page_size), std.os.PROT.READ | std.os.PROT.WRITE, std.os.MAP.ANONYMOUS | std.os.MAP.PRIVATE, 0, 0);
        @memcpy(executable_memory[0..self.assembler.output.items.len], self.assembler.output.items);

        try std.os.mprotect(executable_memory, std.os.PROT.READ | std.os.PROT.EXEC);
#

didn't seem to do anything

#

still segfault at 0x0

#

important to note, if I make (and remove .ptr from slices when calling below)

const func: *const fn (vm: *VM, regs: []Value, locals: []Value) void = @alignCast(@ptrCast(self.code));

to

const func: *const fn (vm: *VM, regs: []Value, locals: []Value) void = @alignCast(@ptrCast(self.code));
#

it's now segfault at 0x8

dusk roost
#

I'd recommend breaking out a debugger and figure out what's actually happening then

thorn vapor
#

uhh

#

I mean I have the assembly it outputs

#

but idk how to really debug it

dusk roost
#

run the program in gdb, set a breakpoint before you enter the jitted code, step through it and figure out what is wrong

thorn vapor
#

breakpoint uh

#

?

#

yeah i'll search that up

#

thanks for helping tho :D

#

so run and then layout asm shows that locals is passed wrongly

dusk roost
#

the first concern I'd have is that zig does not have a stable calling convention

#

and slices do not have a stable representation

thorn vapor
#

callconv (.C)

#

?

dusk roost
#

that's at least defined, yes

thorn vapor
#

sooo

#
 const func: *const fn (vm: *VM, regs: [*]Value, locals: [*]Value) callconv(.C) void = @alignCast(@ptrCast(self.code));
        @call(.auto, func, .{ vm, vm.registers.items.ptr, vm.locals.items.ptr });
#

now gdb progresses further and shows %al, rax

#

which im sure is some wrong assembly output

#

I think I sorted it out

#

hmmm

#

i dont think it's failure of my assembly

#

my binary (jit) does not emit any add, rax or al

thorn vapor
#

jmp 0xe0 (ndisasm) is add al, rax for some reason in gdb

#

note the lines at the left

#

(; is my comment, added in the text file after ndisasm)

#

@pliant herald do you have any idea why?

#

or who is wrong? (ndisasm or gdb)

#

strange...

#

lldb also reports add instead of jmp

(lldb) di -f -m
->  0x7ffff7fee067: addb   %al, (%rax)
    0x7ffff7fee069: addb   %cl, -0x77(%rax)
    0x7ffff7fee06c: xchgb  %al, (%rax)
    0x7ffff7fee06e: addb   %al, (%rax)
    0x7ffff7fee070: addb   %ch, %cl
    0x7ffff7fee072: pushq  $0x0
    0x7ffff7fee074: addb   %al, (%rax) ; same like gdb here
    0x7ffff7fee076: movq   (%rdx), %rax
    0x7ffff7fee07d: movq   %rax, (%rsi)```
#

that's so strange...

dusk roost
#

your addresses seem off

thorn vapor
#

?

#
 for (b.jumps.items) |j| {
    const offset: i64 = @as(i64, @intCast(b.offset)) - @as(i64, @intCast(j - 4));

    self.assembler.output.items[j + 0] = @as(u8, @intCast(offset >> 0 & 0xff));
    self.assembler.output.items[j + 1] = @as(u8, @intCast(offset >> 8 & 0xff));
    self.assembler.output.items[j + 2] = @as(u8, @intCast(offset >> 16 & 0xff));
    self.assembler.output.items[j + 3] = @as(u8, @intCast(offset >> 24 & 0xff));
}
dusk roost
#

the ndisasm is showing jmp at 0x71, but gdb/lldb are showing you the add at at 0x74

thorn vapor
#

hmmm

thorn vapor
#

in c++ it's just

    auto offset = b->offset - j - 4;

    self.assembler.output.items[j + 0] = offset >> 0 & 0xff
    self.assembler.output.items[j + 1] = offset >> 8 & 0xff;
    self.assembler.output.items[j + 2] = offset >> 16 & 0xff;
    self.assembler.output.items[j + 3] = offset >> 24 & 0xff;
dusk roost
#

what is the type of b.offset

thorn vapor
#

size_t

#

a.k. usize

#

so I run translate-c

#
    var a: c_int = (offset >> @intCast(0)) & @as(c_int, 255);
    _ = &a;
    var b: c_int = (offset >> @intCast(8)) & @as(c_int, 255);
    _ = &b;
    var c: c_int = (offset >> @intCast(16)) & @as(c_int, 255);
    _ = &c;
    var d: c_int = (offset >> @intCast(24)) & @as(c_int, 255);
#

hmmmm

thorn vapor
#

but it's to patch the jumps

dusk roost
#

then why not write it as

const offset: u32 = @intCast(b.offset - j - 4);

self.assembler.output.items[j + 0] = @truncate(offset >> 0);
self.assembler.output.items[j + 1] = @truncate(offset >> 8);
self.assembler.output.items[j + 2] = @truncate(offset >> 16);
self.assembler.output.items[j + 3] = @truncate(offset >> 24);

or even

const offset: u32 = @intCast(b.offset - j - 4);
std.mem.writeInt(u32, self.assembler.output.items[j..][0..4], offset, .little);
thorn vapor
#

every time I need to jump, I push to the "jump_stack" the location where I am now

#

and then I emit 32 dead value

#

after compiling, i iterate over each block again, iterate over the "jump_stack"

thorn vapor
#

b.offset can be negative

dusk roost
#

then its not a usize

thorn vapor
#

if it's negative tho, the shifted values aren't

thorn vapor
#

the result of a substraction can be negative

#

it's a RIP-relative 32-bit offset

#

man 😭

thorn vapor
dusk roost
#

if offset is supposed to be an i32, make it an i32

#

at which point you can probably just memcpy the asBytes representation of the offset into the location

thorn vapor
#

I did

thorn vapor
#

wdym by that

#

i found std.mem.asBytes

dusk roost
#

either use mem.writeInt which does this for you, or do the asBytes+memcpy yourself

thorn vapor
#

so I did


const offset: i32 = @as(i32, @intCast(b.offset)) - @as(i32, @intCast(j - 4));

const end = j + 4;
const begin = j;
                @memcpy(self.assembler.output.items[begin..end], std.mem.asBytes(&offset));
#

still crashes

#

same ndisasm output

#

same lldb/gdb thing

dusk roost
#

with the very little snippets of both you've given, the only assumption I can make is that something before 0x71 is encoded wrong or smth

thorn vapor
#

I sent the entire code

#

before

#

but I can update it

#

I made lots of changes

#

also, 0x71?

dusk roost
thorn vapor
#

h mmmm

#

okay

#

let me research more

dusk roost
#

if you could find the gdb/lldb disasm starting from the base address 0x00, that would probably be infinitely more helpful

thorn vapor
#

im trying

#

stupid lldb

#

only lines down

dusk roost
#

disas -a 0x7ffff7fee000 ?

thorn vapor
#

i have a theory

#

so before jump

#

there is a mov rax into rdx

#

which is store_vm_local

#

with .GPR0

#

let me search a little

#

maybe I messed those up

#

that's so weird

#

okay I did something

#

strange

#

I compared my ndisasm with the one in video

#

any instructions before seem to be correct

thorn vapor
#

@dusk roost

#

i think i sorted it out

#

kind of

#

it jumps to wrong place

#

so it parses the instruction wrongly(?)

#

I THINK IT RAN

#

OMG

#

LET'S GO

#

LET'S GOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO

#

(first local = 1 million, good sign)