This fixes the long standing bug with strict aliasing that caused heavily JITed code to miscompile. It stemmed first from pointer reinterpretation which is simply undefined behavior. After fixing that with an `union` and `ffi.copy`, the issue manifested in a different way. Further debugging showed that it was likely that `ffi.copy` (and `ffi.C.memcpy`) would still alias pointers through coercion, more undefined behavior. Now, memory is handled with an `Any` type instead, so aliasing does not occur although unaligned reads and writes may be a problem in the future. I hope this doesn't break. It was very painful to debug.
This commit is contained in:
Rerumu 2021-12-12 01:56:44 -05:00
parent 415f61cb2a
commit 78d4f12bde

View File

@ -298,10 +298,10 @@ end
do do
local load = {} local load = {}
local store = {} local store = {}
local memory = {}
local copy = ffi.copy ffi.cdef [[
union Any {
local RE_INSTANCE = ffi.new [[union {
int8_t i8; int8_t i8;
int16_t i16; int16_t i16;
int32_t i32; int32_t i32;
@ -314,160 +314,12 @@ do
float f32; float f32;
double f64; double f64;
}]] };
function load.i32_i8(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 1)
return RE_INSTANCE.i8
end
function load.i32_u8(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 1)
return RE_INSTANCE.u8
end
function load.i32_i16(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 2)
return RE_INSTANCE.i16
end
function load.i32_u16(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 2)
return RE_INSTANCE.u16
end
function load.i32(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 4)
return RE_INSTANCE.i32
end
function load.i64_i8(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 1)
return i64(RE_INSTANCE.i8)
end
function load.i64_u8(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 1)
return i64(RE_INSTANCE.u8)
end
function load.i64_i16(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 2)
return i64(RE_INSTANCE.i16)
end
function load.i64_u16(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 2)
return i64(RE_INSTANCE.u16)
end
function load.i64_i32(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 4)
return i64(RE_INSTANCE.i32)
end
function load.i64_u32(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 4)
return i64(RE_INSTANCE.u32)
end
function load.i64(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 8)
return RE_INSTANCE.i64
end
function load.f32(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 4)
return RE_INSTANCE.f32
end
function load.f64(memory, addr)
copy(RE_INSTANCE, memory.data + addr, 8)
return RE_INSTANCE.f64
end
function store.i32_n8(memory, addr, value)
RE_INSTANCE.i32 = value
copy(memory.data + addr, RE_INSTANCE, 1)
end
function store.i32_n16(memory, addr, value)
RE_INSTANCE.i32 = value
copy(memory.data + addr, RE_INSTANCE, 2)
end
function store.i32(memory, addr, value)
RE_INSTANCE.i32 = value
copy(memory.data + addr, RE_INSTANCE, 4)
end
function store.i64_n8(memory, addr, value)
RE_INSTANCE.i64 = value
copy(memory.data + addr, RE_INSTANCE, 1)
end
function store.i64_n16(memory, addr, value)
RE_INSTANCE.i64 = value
copy(memory.data + addr, RE_INSTANCE, 2)
end
function store.i64_n32(memory, addr, value)
RE_INSTANCE.i64 = value
copy(memory.data + addr, RE_INSTANCE, 4)
end
function store.i64(memory, addr, value)
RE_INSTANCE.i64 = value
copy(memory.data + addr, RE_INSTANCE, 8)
end
function store.f32(memory, addr, value)
RE_INSTANCE.f32 = value
copy(memory.data + addr, RE_INSTANCE, 4)
end
function store.f64(memory, addr, value)
RE_INSTANCE.f64 = value
copy(memory.data + addr, RE_INSTANCE, 8)
end
module.load = load
module.store = store
end
do
local memory = {}
local WASM_PAGE_SIZE = 65536
ffi.cdef [[
struct Memory { struct Memory {
uint32_t min; uint32_t min;
uint32_t max; uint32_t max;
uint8_t *data; union Any *data;
}; };
void *calloc(size_t num, size_t size); void *calloc(size_t num, size_t size);
@ -475,6 +327,64 @@ do
void free(void *ptr); void free(void *ptr);
]] ]]
local alias_t = ffi.typeof('uint8_t *')
local any_t = ffi.typeof('union Any *')
local cast = ffi.cast
local function by_offset(pointer, offset)
local aliased = cast(alias_t, pointer)
return cast(any_t, aliased + offset)
end
function load.i32_i8(memory, addr) return by_offset(memory.data, addr).i8 end
function load.i32_u8(memory, addr) return by_offset(memory.data, addr).u8 end
function load.i32_i16(memory, addr) return by_offset(memory.data, addr).i16 end
function load.i32_u16(memory, addr) return by_offset(memory.data, addr).u16 end
function load.i32(memory, addr) return by_offset(memory.data, addr).i32 end
function load.i64_i8(memory, addr) return i64(by_offset(memory.data, addr).i8) end
function load.i64_u8(memory, addr) return i64(by_offset(memory.data, addr).u8) end
function load.i64_i16(memory, addr) return i64(by_offset(memory.data, addr).i16) end
function load.i64_u16(memory, addr) return i64(by_offset(memory.data, addr).u16) end
function load.i64_i32(memory, addr) return i64(by_offset(memory.data, addr).i32) end
function load.i64_u32(memory, addr) return i64(by_offset(memory.data, addr).u32) end
function load.i64(memory, addr) return by_offset(memory.data, addr).i64 end
function load.f32(memory, addr) return by_offset(memory.data, addr).f32 end
function load.f64(memory, addr) return by_offset(memory.data, addr).f64 end
function store.i32_n8(memory, addr, value) by_offset(memory.data, addr).i8 = value end
function store.i32_n16(memory, addr, value) by_offset(memory.data, addr).i16 = value end
function store.i32(memory, addr, value) by_offset(memory.data, addr).i32 = value end
function store.i64_n8(memory, addr, value) by_offset(memory.data, addr).i8 = value end
function store.i64_n16(memory, addr, value) by_offset(memory.data, addr).i16 = value end
function store.i64_n32(memory, addr, value) by_offset(memory.data, addr).i32 = value end
function store.i64(memory, addr, value) by_offset(memory.data, addr).i64 = value end
function store.f32(memory, addr, value) by_offset(memory.data, addr).f32 = value end
function store.f64(memory, addr, value) by_offset(memory.data, addr).f64 = value end
local WASM_PAGE_SIZE = 65536
local function finalizer(memory) ffi.C.free(memory.data) end local function finalizer(memory) ffi.C.free(memory.data) end
local function grow_unchecked(memory, old, new) local function grow_unchecked(memory, old, new)
@ -482,7 +392,7 @@ do
assert(memory.data ~= nil, 'failed to reallocate') assert(memory.data ~= nil, 'failed to reallocate')
ffi.fill(memory.data + old, new - old, 0) ffi.fill(by_offset(memory.data, old), new - old, 0)
end end
function memory.new(min, max) function memory.new(min, max)
@ -495,7 +405,7 @@ do
return ffi.gc(memory, finalizer) return ffi.gc(memory, finalizer)
end end
function memory.init(memory, offset, data) ffi.copy(memory.data + offset, data) end function memory.init(memory, addr, data) ffi.copy(by_offset(memory.data, addr), data) end
function memory.grow(memory, num) function memory.grow(memory, num)
local old = memory.min local old = memory.min
@ -511,6 +421,8 @@ do
end end
end end
module.load = load
module.store = store
module.memory = memory module.memory = memory
end end