blob: e5a09c17f7844d42cae2b395f33079968fde8f4f [file] [log] [blame]
#include <stdint.h>
#include <protobuf.h>
// -----------------------------------------------------------------------------
// PHP <-> native slot management.
// -----------------------------------------------------------------------------
static zval* int32_to_zval(int32_t value) {
zval* tmp;
MAKE_STD_ZVAL(tmp);
ZVAL_LONG(tmp, value);
php_printf("int32 to zval\n");
// ZVAL_LONG(tmp, 1);
return tmp;
}
#define DEREF(memory, type) *(type*)(memory)
size_t native_slot_size(upb_fieldtype_t type) {
switch (type) {
case UPB_TYPE_FLOAT: return 4;
case UPB_TYPE_DOUBLE: return 8;
case UPB_TYPE_BOOL: return 1;
case UPB_TYPE_STRING: return sizeof(zval*);
case UPB_TYPE_BYTES: return sizeof(zval*);
case UPB_TYPE_MESSAGE: return sizeof(zval*);
case UPB_TYPE_ENUM: return 4;
case UPB_TYPE_INT32: return 4;
case UPB_TYPE_INT64: return 8;
case UPB_TYPE_UINT32: return 4;
case UPB_TYPE_UINT64: return 8;
default: return 0;
}
}
static bool is_php_num(zval* value) {
// Is numerial string also valid?
return (Z_TYPE_P(value) == IS_LONG ||
Z_TYPE_P(value) == IS_DOUBLE);
}
void native_slot_check_int_range_precision(upb_fieldtype_t type, zval* val) {
// TODO(teboring): Add it back.
// if (!is_php_num(val)) {
// zend_error(E_ERROR, "Expected number type for integral field.");
// }
// if (Z_TYPE_P(val) == IS_DOUBLE) {
// double dbl_val = NUM2DBL(val);
// if (floor(dbl_val) != dbl_val) {
// zend_error(E_ERROR,
// "Non-integral floating point value assigned to integer field.");
// }
// }
// if (type == UPB_TYPE_UINT32 || type == UPB_TYPE_UINT64) {
// if (NUM2DBL(val) < 0) {
// zend_error(E_ERROR,
// "Assigning negative value to unsigned integer field.");
// }
// }
}
zval* native_slot_get(upb_fieldtype_t type, /*VALUE type_class,*/
const void* memory TSRMLS_DC) {
zval* retval = NULL;
switch (type) {
// TODO(teboring): Add it back.
// case UPB_TYPE_FLOAT:
// return DBL2NUM(DEREF(memory, float));
// case UPB_TYPE_DOUBLE:
// return DBL2NUM(DEREF(memory, double));
// case UPB_TYPE_BOOL:
// return DEREF(memory, int8_t) ? Qtrue : Qfalse;
// case UPB_TYPE_STRING:
// case UPB_TYPE_BYTES:
// case UPB_TYPE_MESSAGE:
// return DEREF(memory, VALUE);
// case UPB_TYPE_ENUM: {
// int32_t val = DEREF(memory, int32_t);
// VALUE symbol = enum_lookup(type_class, INT2NUM(val));
// if (symbol == Qnil) {
// return INT2NUM(val);
// } else {
// return symbol;
// }
// }
case UPB_TYPE_INT32:
return int32_to_zval(DEREF(memory, int32_t));
// TODO(teboring): Add it back.
// case UPB_TYPE_INT64:
// return LL2NUM(DEREF(memory, int64_t));
// case UPB_TYPE_UINT32:
// return UINT2NUM(DEREF(memory, uint32_t));
// case UPB_TYPE_UINT64:
// return ULL2NUM(DEREF(memory, uint64_t));
default:
return EG(uninitialized_zval_ptr);
}
}
void native_slot_init(upb_fieldtype_t type, void* memory) {
switch (type) {
case UPB_TYPE_FLOAT:
DEREF(memory, float) = 0.0;
break;
case UPB_TYPE_DOUBLE:
DEREF(memory, double) = 0.0;
break;
case UPB_TYPE_BOOL:
DEREF(memory, int8_t) = 0;
break;
// TODO(teboring): Add it back.
// case UPB_TYPE_STRING:
// case UPB_TYPE_BYTES:
// DEREF(memory, VALUE) = php_str_new2("");
// php_enc_associate(DEREF(memory, VALUE), (type == UPB_TYPE_BYTES)
// ? kRubyString8bitEncoding
// : kRubyStringUtf8Encoding);
// break;
// case UPB_TYPE_MESSAGE:
// DEREF(memory, VALUE) = Qnil;
// break;
case UPB_TYPE_ENUM:
case UPB_TYPE_INT32:
DEREF(memory, int32_t) = 0;
break;
case UPB_TYPE_INT64:
DEREF(memory, int64_t) = 0;
break;
case UPB_TYPE_UINT32:
DEREF(memory, uint32_t) = 0;
break;
case UPB_TYPE_UINT64:
DEREF(memory, uint64_t) = 0;
break;
default:
break;
}
}
void native_slot_set(upb_fieldtype_t type, /*VALUE type_class,*/ void* memory,
zval* value) {
native_slot_set_value_and_case(type, /*type_class,*/ memory, value, NULL, 0);
}
void native_slot_set_value_and_case(upb_fieldtype_t type, /*VALUE type_class,*/
void* memory, zval* value,
uint32_t* case_memory,
uint32_t case_number) {
switch (type) {
case UPB_TYPE_FLOAT:
if (!Z_TYPE_P(value) == IS_LONG) {
zend_error(E_ERROR, "Expected number type for float field.");
}
DEREF(memory, float) = Z_DVAL_P(value);
break;
case UPB_TYPE_DOUBLE:
// TODO(teboring): Add it back.
// if (!is_php_num(value)) {
// zend_error(E_ERROR, "Expected number type for double field.");
// }
// DEREF(memory, double) = Z_DVAL_P(value);
break;
case UPB_TYPE_BOOL: {
int8_t val = -1;
if (zval_is_true(value)) {
val = 1;
} else {
val = 0;
}
// TODO(teboring): Add it back.
// else if (value == Qfalse) {
// val = 0;
// }
// else {
// php_raise(php_eTypeError, "Invalid argument for boolean field.");
// }
DEREF(memory, int8_t) = val;
break;
}
case UPB_TYPE_STRING:
case UPB_TYPE_BYTES: {
// TODO(teboring): Add it back.
// if (Z_TYPE_P(value) != IS_STRING) {
// zend_error(E_ERROR, "Invalid argument for string field.");
// }
// native_slot_validate_string_encoding(type, value);
// DEREF(memory, zval*) = value;
break;
}
case UPB_TYPE_MESSAGE: {
// TODO(teboring): Add it back.
// if (CLASS_OF(value) == CLASS_OF(Qnil)) {
// value = Qnil;
// } else if (CLASS_OF(value) != type_class) {
// php_raise(php_eTypeError,
// "Invalid type %s to assign to submessage field.",
// php_class2name(CLASS_OF(value)));
// }
// DEREF(memory, VALUE) = value;
break;
}
case UPB_TYPE_ENUM: {
// TODO(teboring): Add it back.
// int32_t int_val = 0;
// if (!is_php_num(value) && TYPE(value) != T_SYMBOL) {
// php_raise(php_eTypeError,
// "Expected number or symbol type for enum field.");
// }
// if (TYPE(value) == T_SYMBOL) {
// // Ensure that the given symbol exists in the enum module.
// VALUE lookup = php_funcall(type_class, php_intern("resolve"), 1, value);
// if (lookup == Qnil) {
// php_raise(php_eRangeError, "Unknown symbol value for enum field.");
// } else {
// int_val = NUM2INT(lookup);
// }
// } else {
// native_slot_check_int_range_precision(UPB_TYPE_INT32, value);
// int_val = NUM2INT(value);
// }
// DEREF(memory, int32_t) = int_val;
// break;
}
case UPB_TYPE_INT32:
case UPB_TYPE_INT64:
case UPB_TYPE_UINT32:
case UPB_TYPE_UINT64:
native_slot_check_int_range_precision(type, value);
switch (type) {
case UPB_TYPE_INT32:
php_printf("Setting INT32 field\n");
DEREF(memory, int32_t) = Z_LVAL_P(value);
break;
case UPB_TYPE_INT64:
// TODO(teboring): Add it back.
// DEREF(memory, int64_t) = NUM2LL(value);
break;
case UPB_TYPE_UINT32:
// TODO(teboring): Add it back.
// DEREF(memory, uint32_t) = NUM2UINT(value);
break;
case UPB_TYPE_UINT64:
// TODO(teboring): Add it back.
// DEREF(memory, uint64_t) = NUM2ULL(value);
break;
default:
break;
}
break;
default:
break;
}
if (case_memory != NULL) {
*case_memory = case_number;
}
}
// -----------------------------------------------------------------------------
// Map field utilities.
// ----------------------------------------------------------------------------
const upb_msgdef* tryget_map_entry_msgdef(const upb_fielddef* field) {
const upb_msgdef* subdef;
if (upb_fielddef_label(field) != UPB_LABEL_REPEATED ||
upb_fielddef_type(field) != UPB_TYPE_MESSAGE) {
return NULL;
}
subdef = upb_fielddef_msgsubdef(field);
return upb_msgdef_mapentry(subdef) ? subdef : NULL;
}
const upb_msgdef* map_entry_msgdef(const upb_fielddef* field) {
const upb_msgdef* subdef = tryget_map_entry_msgdef(field);
assert(subdef);
return subdef;
}
bool is_map_field(const upb_fielddef* field) {
return tryget_map_entry_msgdef(field) != NULL;
}
// -----------------------------------------------------------------------------
// Memory layout management.
// -----------------------------------------------------------------------------
static size_t align_up_to(size_t offset, size_t granularity) {
// Granularity must be a power of two.
return (offset + granularity - 1) & ~(granularity - 1);
}
MessageLayout* create_layout(const upb_msgdef* msgdef) {
MessageLayout* layout = ALLOC(MessageLayout);
int nfields = upb_msgdef_numfields(msgdef);
upb_msg_field_iter it;
upb_msg_oneof_iter oit;
size_t off = 0;
layout->fields = ALLOC_N(MessageField, nfields);
for (upb_msg_field_begin(&it, msgdef); !upb_msg_field_done(&it);
upb_msg_field_next(&it)) {
const upb_fielddef* field = upb_msg_iter_field(&it);
size_t field_size;
if (upb_fielddef_containingoneof(field)) {
// Oneofs are handled separately below.
continue;
}
// Allocate |field_size| bytes for this field in the layout.
field_size = 0;
if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
field_size = sizeof(zval*);
} else {
field_size = native_slot_size(upb_fielddef_type(field));
}
// Align current offset up to | size | granularity.
off = align_up_to(off, field_size);
layout->fields[upb_fielddef_index(field)].offset = off;
layout->fields[upb_fielddef_index(field)].case_offset =
MESSAGE_FIELD_NO_CASE;
off += field_size;
}
// Handle oneofs now -- we iterate over oneofs specifically and allocate only
// one slot per oneof.
//
// We assign all value slots first, then pack the 'case' fields at the end,
// since in the common case (modern 64-bit platform) these are 8 bytes and 4
// bytes respectively and we want to avoid alignment overhead.
//
// Note that we reserve 4 bytes (a uint32) per 'case' slot because the value
// space for oneof cases is conceptually as wide as field tag numbers. In
// practice, it's unlikely that a oneof would have more than e.g. 256 or 64K
// members (8 or 16 bits respectively), so conceivably we could assign
// consecutive case numbers and then pick a smaller oneof case slot size, but
// the complexity to implement this indirection is probably not worthwhile.
for (upb_msg_oneof_begin(&oit, msgdef); !upb_msg_oneof_done(&oit);
upb_msg_oneof_next(&oit)) {
const upb_oneofdef* oneof = upb_msg_iter_oneof(&oit);
upb_oneof_iter fit;
// Always allocate NATIVE_SLOT_MAX_SIZE bytes, but share the slot between
// all fields.
size_t field_size = NATIVE_SLOT_MAX_SIZE;
// Align the offset .
off = align_up_to( off, field_size);
// Assign all fields in the oneof this same offset.
for (upb_oneof_begin(&fit, oneof); !upb_oneof_done(&fit);
upb_oneof_next(&fit)) {
const upb_fielddef* field = upb_oneof_iter_field(&fit);
layout->fields[upb_fielddef_index(field)].offset = off;
}
off += field_size;
}
// Now the case fields.
for (upb_msg_oneof_begin(&oit, msgdef); !upb_msg_oneof_done(&oit);
upb_msg_oneof_next(&oit)) {
const upb_oneofdef* oneof = upb_msg_iter_oneof(&oit);
upb_oneof_iter fit;
size_t field_size = sizeof(uint32_t);
// Align the offset .
off = (off + field_size - 1) & ~(field_size - 1);
// Assign all fields in the oneof this same offset.
for (upb_oneof_begin(&fit, oneof); !upb_oneof_done(&fit);
upb_oneof_next(&fit)) {
const upb_fielddef* field = upb_oneof_iter_field(&fit);
layout->fields[upb_fielddef_index(field)].case_offset = off;
}
off += field_size;
}
layout->size = off;
layout->msgdef = msgdef;
upb_msgdef_ref(layout->msgdef, &layout->msgdef);
return layout;
}
void free_layout(MessageLayout* layout) {
FREE(layout->fields);
upb_msgdef_unref(layout->msgdef, &layout->msgdef);
FREE(layout);
}
// TODO(teboring): Add it back.
// VALUE field_type_class(const upb_fielddef* field) {
// VALUE type_class = Qnil;
// if (upb_fielddef_type(field) == UPB_TYPE_MESSAGE) {
// VALUE submsgdesc = get_def_obj(upb_fielddef_subdef(field));
// type_class = Descriptor_msgclass(submsgdesc);
// } else if (upb_fielddef_type(field) == UPB_TYPE_ENUM) {
// VALUE subenumdesc = get_def_obj(upb_fielddef_subdef(field));
// type_class = EnumDescriptor_enummodule(subenumdesc);
// }
// return type_class;
// }
static void* slot_memory(MessageLayout* layout, const void* storage,
const upb_fielddef* field) {
return ((uint8_t*)storage) + layout->fields[upb_fielddef_index(field)].offset;
}
static uint32_t* slot_oneof_case(MessageLayout* layout, const void* storage,
const upb_fielddef* field) {
return (uint32_t*)(((uint8_t*)storage) +
layout->fields[upb_fielddef_index(field)].case_offset);
}
void layout_set(MessageLayout* layout, void* storage, const upb_fielddef* field,
zval* val) {
void* memory = slot_memory(layout, storage, field);
uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
if (upb_fielddef_containingoneof(field)) {
if (Z_TYPE_P(val) == IS_NULL) {
// Assigning nil to a oneof field clears the oneof completely.
*oneof_case = ONEOF_CASE_NONE;
memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
} else {
// The transition between field types for a single oneof (union) slot is
// somewhat complex because we need to ensure that a GC triggered at any
// point by a call into the Ruby VM sees a valid state for this field and
// does not either go off into the weeds (following what it thinks is a
// VALUE but is actually a different field type) or miss an object (seeing
// what it thinks is a primitive field but is actually a VALUE for the new
// field type).
//
// In order for the transition to be safe, the oneof case slot must be in
// sync with the value slot whenever the Ruby VM has been called. Thus, we
// use native_slot_set_value_and_case(), which ensures that both the value
// and case number are altered atomically (w.r.t. the Ruby VM).
native_slot_set_value_and_case(upb_fielddef_type(field),
/*field_type_class(field),*/ memory, val,
oneof_case, upb_fielddef_number(field));
}
} else if (is_map_field(field)) {
// TODO(teboring): Add it back.
// check_map_field_type(val, field);
// DEREF(memory, zval*) = val;
} else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
// TODO(teboring): Add it back.
// check_repeated_field_type(val, field);
// DEREF(memory, zval*) = val;
} else {
native_slot_set(upb_fielddef_type(field), /*field_type_class(field),*/ memory,
val);
}
}
void layout_init(MessageLayout* layout, void* storage) {
upb_msg_field_iter it;
for (upb_msg_field_begin(&it, layout->msgdef); !upb_msg_field_done(&it);
upb_msg_field_next(&it)) {
const upb_fielddef* field = upb_msg_iter_field(&it);
void* memory = slot_memory(layout, storage, field);
uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
if (upb_fielddef_containingoneof(field)) {
// TODO(teboring): Add it back.
// memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
// *oneof_case = ONEOF_CASE_NONE;
} else if (is_map_field(field)) {
// TODO(teboring): Add it back.
// VALUE map = Qnil;
// const upb_fielddef* key_field = map_field_key(field);
// const upb_fielddef* value_field = map_field_value(field);
// VALUE type_class = field_type_class(value_field);
// if (type_class != Qnil) {
// VALUE args[3] = {
// fieldtype_to_php(upb_fielddef_type(key_field)),
// fieldtype_to_php(upb_fielddef_type(value_field)), type_class,
// };
// map = php_class_new_instance(3, args, cMap);
// } else {
// VALUE args[2] = {
// fieldtype_to_php(upb_fielddef_type(key_field)),
// fieldtype_to_php(upb_fielddef_type(value_field)),
// };
// map = php_class_new_instance(2, args, cMap);
// }
// DEREF(memory, VALUE) = map;
} else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
// TODO(teboring): Add it back.
// VALUE ary = Qnil;
// VALUE type_class = field_type_class(field);
// if (type_class != Qnil) {
// VALUE args[2] = {
// fieldtype_to_php(upb_fielddef_type(field)), type_class,
// };
// ary = php_class_new_instance(2, args, cRepeatedField);
// } else {
// VALUE args[1] = {fieldtype_to_php(upb_fielddef_type(field))};
// ary = php_class_new_instance(1, args, cRepeatedField);
// }
// DEREF(memory, VALUE) = ary;
} else {
native_slot_init(upb_fielddef_type(field), memory);
}
}
}
zval* layout_get(MessageLayout* layout, const void* storage,
const upb_fielddef* field TSRMLS_DC) {
void* memory = slot_memory(layout, storage, field);
uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
if (upb_fielddef_containingoneof(field)) {
if (*oneof_case != upb_fielddef_number(field)) {
return NULL;
// TODO(teboring): Add it back.
// return Qnil;
}
return NULL;
// TODO(teboring): Add it back.
// return native_slot_get(upb_fielddef_type(field), field_type_class(field),
// memory);
} else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
return NULL;
// TODO(teboring): Add it back.
// return *((VALUE*)memory);
} else {
return native_slot_get(
upb_fielddef_type(field), /*field_type_class(field), */
memory TSRMLS_CC);
}
}