Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions spy/backend/c/cstructwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from spy.backend.c.context import C_Type, Context
from spy.fqn import FQN
from spy.textbuilder import TextBuilder
from spy.vm.modules.unsafe.misc import contains_gc_ptr
from spy.vm.modules.unsafe.misc import alignof, contains_gc_ptr
from spy.vm.modules.unsafe.ptr import W_PtrType, W_RefType
from spy.vm.object import W_Type
from spy.vm.struct import W_StructType
Expand Down Expand Up @@ -171,6 +171,12 @@ def emit_PtrType(self, fqn: FQN, w_ptrtype: W_PtrType) -> None:
self.tbh_fwdecl.wl(
f"// {c_ptrtype}: skip as it's already pre-declared by libspy"
)
# The struct typedef and SPY_PTR_FUNCTIONS are hand-written in
# libspy for these types, but the per-field unaligned helpers
# are NOT (they depend on the field layout of the *item* type,
# which the C backend, not libspy, knows about) -- so those
# still need to be emitted here if needed.
self._emit_ptr_field_helpers(w_ptrtype)
return

self.tbh_fwdecl.wb(f"""
Expand All @@ -196,12 +202,54 @@ def emit_PtrType(self, fqn: FQN, w_ptrtype: W_PtrType) -> None:
# to scan it. See spy/libspy/include/spy/unsafe.h.
alloc_func = "gc_alloc_pointerless"

alignment = w_ptrtype.alignment
self.tbh_ptrs_def.wb(f"""
SPY_PTR_FUNCTIONS({alloc_func}, {c_ptrtype}, {c_itemT});
SPY_PTR_FUNCTIONS({alloc_func}, {c_ptrtype}, {c_itemT}, {alignment});
#define {c_ptrtype}$NULL (({c_ptrtype}){{0}})
""")
self.tbh_ptrs_def.wl()

# Emit per-field unaligned helpers if the ptr may be under-aligned
# relative to one of the item struct's fields.
self._emit_ptr_field_helpers(w_ptrtype)

def _emit_ptr_field_helpers(self, w_ptrtype: W_PtrType) -> None:
"""
Emit unaligned load/store helpers for struct fields accessed
through an under-aligned ptr (see fmt_ptr_getfield/fmt_ptr_setfield
in cwriter.py, which decide when to call these instead of a plain
typed field access).
"""
w_itemT = w_ptrtype.w_itemT
if not isinstance(w_itemT, W_StructType) or not w_itemT.is_defined():
return

c_ptrtype = C_Type(w_ptrtype.fqn.c_name)
ptr_align = w_ptrtype.alignment

for w_field in w_itemT.iterfields_w():
field_align = alignof(w_field.w_T)
if ptr_align >= field_align:
continue

c_fieldtype = self.ctx.w2c(w_field.w_T)
c_fieldname = w_field.name

self.tbh_ptrs_def.wb(f"""
static inline {c_fieldtype} {c_ptrtype}$getfield_{c_fieldname}_unaligned(
{c_ptrtype} p) {{
{c_fieldtype} _tmp;
__builtin_memcpy(&_tmp, (const char *)p.p + {w_field.offset}, sizeof({c_fieldtype}));
return _tmp;
}}

static inline void {c_ptrtype}$setfield_{c_fieldname}_unaligned(
{c_ptrtype} p, {c_fieldtype} v) {{
__builtin_memcpy((char *)p.p + {w_field.offset}, &v, sizeof({c_fieldtype}));
}}
""")
self.tbh_ptrs_def.wl()

def emit_RefType(self, fqn: FQN, w_reftype: W_RefType) -> None:
w_ptrtype = w_reftype.as_ptrtype(self.ctx.vm)
c_reftype = C_Type(w_reftype.fqn.c_name)
Expand Down
58 changes: 56 additions & 2 deletions spy/backend/c/cwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
from spy.vm.function import W_ASTFunc, W_Func
from spy.vm.irtag import IRTag
from spy.vm.modules.posix import W__FILE
from spy.vm.modules.unsafe.ptr import W_Ptr
from spy.vm.modules.unsafe.misc import alignof
from spy.vm.modules.unsafe.ptr import W_Ptr, W_PtrType
from spy.vm.struct import W_StructType

if TYPE_CHECKING:
from spy.backend.c.cmodwriter import CModuleWriter
Expand Down Expand Up @@ -590,6 +592,9 @@ def fmt_expr_Call(self, call: ast.Call) -> C.Expr:
# we handle ptr.deref explicitly for extra clarity
return self.fmt_generic_call(fqn, call)

elif irtag.tag == "ptr.weaken_align":
return self.fmt_ptr_weaken_align(fqn, call)

elif irtag.tag in ("ptr.getitem", "ptr.store"):
# see unsafe/ptr.py::w_GETITEM and w_SETITEM there, we insert an
# extra "w_loc" argument, which is not needed by the C backend
Expand Down Expand Up @@ -632,11 +637,53 @@ def fmt_struct_getfield(self, fqn: FQN, call: ast.Call, irtag: IRTag) -> C.Expr:
name = irtag.data["name"]
return C.Dot(c_struct, name)

def fmt_ptr_weaken_align(self, fqn: FQN, call: ast.Call) -> C.Expr:
"""
gc_ptr[T,N] -> gc_ptr[T,M] weakening conversion. Both types have
byte-identical C layout ({T *p; length}), so this is just a
relabeling.
"""
assert len(call.args) == 1
w_srcT = call.args[0].w_T
assert w_srcT is not None
c_src = self.fmt_expr(call.args[0])
c_srctype = self.ctx.w2c(w_srcT)
c_targettype = self.ctx.c_restype_by_fqn(fqn)
c_p = C.Literal(f"({c_src}).p")
c_length = C.Call(f"{c_srctype}_get_length", [c_src])
return C.Call(f"{c_targettype}_from_raw", [c_p, c_length])

def _is_under_aligned_field(self, w_ptr: object, attr: str) -> bool:
"""
True if w_ptr is a W_PtrType pointing to a defined struct, and
`attr` names a field whose natural alignment exceeds the ptr's
declared alignment -- i.e. a plain typed access to it would be
undefined behavior and must instead go through the
$getfield_*_unaligned / $setfield_*_unaligned helpers emitted by
CStructWriter._emit_ptr_field_helpers.
"""
if not isinstance(w_ptr, W_PtrType):
return False
w_itemT = w_ptr.w_itemT
if not isinstance(w_itemT, W_StructType) or not w_itemT.is_defined():
return False
for w_field in w_itemT.iterfields_w():
if w_field.name == attr:
return w_ptr.alignment < alignof(w_field.w_T)
return False

def fmt_ptr_getfield(self, fqn: FQN, call: ast.Call, irtag: IRTag) -> C.Expr:
assert isinstance(call.args[1], ast.StrLiteral)
c_ptr = self.fmt_expr(call.args[0])
attr = call.args[1].value
offset = call.args[2] # ignored

w_ptr = call.args[0].w_T
if self._is_under_aligned_field(w_ptr, attr):
assert w_ptr is not None
c_ptrtype = self.ctx.w2c(w_ptr)
return C.Call(f"{c_ptrtype}$getfield_{attr}_unaligned", [c_ptr])

c_field = C.PtrField(c_ptr, attr)
if irtag.data["by"] == "byref":
c_restype = self.ctx.c_restype_by_fqn(fqn)
Expand All @@ -649,8 +696,15 @@ def fmt_ptr_setfield(self, fqn: FQN, call: ast.Call) -> C.Expr:
c_ptr = self.fmt_expr(call.args[0])
attr = call.args[1].value
offset = call.args[2] # ignored
c_lval = C.PtrField(c_ptr, attr)
c_rval = self.fmt_expr(call.args[3])

w_ptr = call.args[0].w_T
if self._is_under_aligned_field(w_ptr, attr):
assert w_ptr is not None
c_ptrtype = self.ctx.w2c(w_ptr)
return C.Call(f"{c_ptrtype}$setfield_{attr}_unaligned", [c_ptr, c_rval])

c_lval = C.PtrField(c_ptr, attr)
return C.BinOp("=", c_lval, c_rval)

def fmt_memop(self, fqn: FQN, call: ast.Call, irtag: IRTag) -> C.Expr:
Expand Down
2 changes: 1 addition & 1 deletion spy/libspy/include/spy/bytes.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ typedef struct spy_unsafe$gc_ptr___bytes$BytesObject {
#endif
} spy_unsafe$gc_ptr___bytes$BytesObject;

SPY_PTR_FUNCTIONS(gc_alloc, spy_unsafe$gc_ptr___bytes$BytesObject, spy_BytesObject)
SPY_PTR_FUNCTIONS(gc_alloc, spy_unsafe$gc_ptr___bytes$BytesObject, spy_BytesObject, 4)
#define spy_unsafe$gc_ptr___bytes$BytesObject$NULL \
((spy_unsafe$gc_ptr___bytes$BytesObject){0})

Expand Down
2 changes: 1 addition & 1 deletion spy/libspy/include/spy/str.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ typedef struct spy_unsafe$gc_ptr___str$StrObject {
#endif
} spy_unsafe$gc_ptr___str$StrObject;

SPY_PTR_FUNCTIONS(gc_alloc, spy_unsafe$gc_ptr___str$StrObject, spy_StrObject)
SPY_PTR_FUNCTIONS(gc_alloc, spy_unsafe$gc_ptr___str$StrObject, spy_StrObject, 4)
#define spy_unsafe$gc_ptr___str$StrObject$NULL ((spy_unsafe$gc_ptr___str$StrObject){0})

// short alias for manual use
Expand Down
Loading
Loading