[presentations] master: php[tek] 23 tutorial
[email protected] (Derick Rethans) Fri, 12 May 2023 17:14:13 +0000
| Newsgroups | php.pres |
|---|---|
| Message-ID | <[email protected]> |
Author: Derick Rethans (derickr) Date: 2023-05-12T18:00:43+01:00 Commit: https://github.com/php/presentations/commit/6f8db6a3964e2bc41a794ed6dd5ce10f164b7add Raw diff: https://github.com/php/presentations/commit/6f8db6a3964e2bc41a794ed6dd5ce10f164b7add.diff php[tek] 23 tutorial Changed paths: A php-tek23.xml A slides/internals/collection-add-add-handler-implementation.xml A slides/internals/collection-add-add-handler-implementation2.xml A slides/internals/collection-add-add-handler.xml A slides/internals/collection-add-ast-clean-up.xml A slides/internals/collection-add-ast-type-check.xml A slides/internals/collection-add-ast.xml A slides/internals/collection-add-has-handler.xml A slides/internals/collection-add-init-and-property.xml A slides/internals/collection-add-interface-handlers.xml A slides/internals/collection-add-interface.xml A slides/internals/collection-add-parser-rules.xml A slides/internals/collection-add-read-handler.xml A slides/internals/collection-add-read-handler2.xml A slides/internals/collection-add-token.xml A slides/internals/collection-add-unset-handler.xml A slides/internals/collection-hook-init-and-property.xml A slides/internals/collection-make-check-type-public.xml A slides/internals/collection-setup-interface-handlers.xml A slides/internals/collection-syntax-example.xml M slides/internals/recap-content.xml M slides/mongodb/me.xml Diff: diff --git a/php-tek23.xml b/php-tek23.xml new file mode 100644 index 00000000..9a92e0f2 --- /dev/null +++ b/php-tek23.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> +<presentation css="derick.css"> +<topic>PHP</topic> +<title>PHP Internals Deep Dive</title> +<event>php[tek] 2023</event> +<location>Chicago, IL, USA</location> +<date>May 16h, 2023</date> +<speaker>Derick Rethans</speaker> +<email>[email protected]</email> +<twitter>derickr</twitter> +<mastodon>@[email protected]</mastodon> +<url>https://derickrethans.nl/talks/php-tek23</url> +<joindin>https://joind.in/29096</joindin> + +<slide>slides/xdebug/title.xml</slide> +<slide>slides/mongodb/me.xml</slide> + +<slide>slides/internals/title-agenda.xml</slide> + +<slide>slides/internals/title-stages.xml</slide> +<!-- INTRODUCTION +Compile stages: +Parse script +Create AST +Convert to opcodes +Run the code +Opcache for caching +--> +<slide>slides/internals/stages.xml</slide> + +<!-- STAGES +Parse +- Show parser rules +- Show "compiler" rules +--> + +<slide>slides/internals/collection-syntax-example.xml</slide> + +<slide>slides/internals/title-parsing.xml</slide> +<slide>slides/internals/parse-state-machine.xml</slide> +<slide>slides/internals/parse-tokenization.xml</slide> + +<slide>slides/internals/collection-add-token.xml</slide> + +<slide>slides/internals/scanner.xml</slide> +<slide>slides/internals/scanner-rules.xml</slide> + +<slide>slides/internals/collection-add-parser-rules.xml</slide> + +<slide>slides/internals/ast.xml</slide> +<slide>slides/internals/ast1.xml</slide> +<slide>slides/internals/scanner-ast.xml</slide> +<slide>slides/internals/scanner-ast2.xml</slide> + +<slide>slides/internals/collection-add-ast.xml</slide> +<slide>slides/internals/collection-add-ast-type-check.xml</slide> +<slide>slides/internals/collection-add-ast-clean-up.xml</slide> + +<slide>slides/internals/collection-add-init-and-property.xml</slide> +<slide>slides/internals/collection-hook-init-and-property.xml</slide> +<slide>slides/internals/collection-add-interface.xml</slide> +<slide>slides/internals/collection-add-interface-handlers.xml</slide> +<slide>slides/internals/collection-setup-interface-handlers.xml</slide> +<slide>slides/internals/collection-add-add-handler.xml</slide> +<slide>slides/internals/collection-add-add-handler-implementation.xml</slide> +<slide>slides/internals/collection-add-add-handler-implementation2.xml</slide> +<slide>slides/internals/collection-make-check-type-public.xml</slide> +<slide>slides/internals/collection-add-read-handler.xml</slide> +<slide>slides/internals/collection-add-read-handler2.xml</slide> +<slide>slides/internals/collection-add-has-handler.xml</slide> +<slide>slides/internals/collection-add-unset-handler.xml</slide> + +<slide>slides/internals/bytecode0.xml</slide> +<slide>slides/internals/bytecode½.xml</slide> +<slide>slides/internals/bytecode1.xml</slide> + +<!-- +AST +- Show AST for various control structures + +Opcodes +- Show AST for the same control structures +--> + +<slide>slides/internals/jumps-for.xml</slide> +<slide>slides/internals/jumps-for-rewritten.xml</slide> +<slide>slides/internals/jumps-complex-dot.xml</slide> + +<!-- Conclusion + +Code -> Tokens -> AST -> opcode + +Transformation of control structures + +Useful implementations + +--> +<slide>slides/internals/recap.xml</slide> +<slide>slides/internals/recap-content.xml</slide> + + + +<slide>slides/mongodb/questions.xml</slide> +<slide>slides/xdebug/phpinternalsnews.xml</slide> + +<slide>slides/mongodb/resources.xml</slide> + +</presentation> diff --git a/slides/internals/collection-add-add-handler-implementation.xml b/slides/internals/collection-add-add-handler-implementation.xml new file mode 100644 index 00000000..18635226 --- /dev/null +++ b/slides/internals/collection-add-add-handler-implementation.xml @@ -0,0 +1,46 @@ +<slide> +<title>Collection: Add Write Handler Implementation [1]</title> + +<blurb>%Zend/zend_collection.c%</blurb> +<example class="small"><![CDATA[ +void zend_collection_add_item(zend_object *object, zval *offset, zval *value) +{ + zend_class_entry *ce = object->ce; + zval rv; + zval *value_prop; + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + + if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { + zend_string *type_str = zend_type_to_string(ce->collection_item_type); + zend_type_error( + "Value type %s does not match collection item type %s", + zend_zval_type_name(value), ZSTR_VAL(type_str)); + zend_string_release(type_str); + return; + } + + if (!offset && ce->collection_key_type == IS_LONG) { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + Z_ADDREF_P(value); + add_next_index_zval(value_prop, value); + } else if (offset && ce->collection_key_type == IS_LONG && Z_TYPE_P(offset) == IS_LONG) { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + Z_ADDREF_P(value); + add_index_zval(value_prop, Z_LVAL_P(offset), value); + } else if (offset && ce->collection_key_type == IS_STRING && Z_TYPE_P(offset) == IS_STRING) { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + Z_ADDREF_P(value); + add_assoc_zval_ex(value_prop, Z_STRVAL_P(offset), Z_STRLEN_P(offset), value); + } else { + zend_type_error( + "Key type %s of element does not match collection key type %s", + offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), + zend_get_type_by_const(ce->collection_key_type)); + } +} +]]></example> + +</slide> diff --git a/slides/internals/collection-add-add-handler-implementation2.xml b/slides/internals/collection-add-add-handler-implementation2.xml new file mode 100644 index 00000000..dc17c226 --- /dev/null +++ b/slides/internals/collection-add-add-handler-implementation2.xml @@ -0,0 +1,21 @@ +<slide> +<title>Collection: Add Write Handler Implementation [2]</title> + +<blurb>%Zend/zend_collection.c%</blurb> +<example class="small"><![CDATA[ +static void create_array_if_needed(zend_class_entry *ce, zend_object *object) +{ + zval *value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + if (Z_TYPE_P(value_prop) == IS_ARRAY) { + return; + } + + zval new_array; + array_init(&new_array); + zend_update_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), &new_array); + zval_ptr_dtor(&new_array); +} +]]></example> + +</slide> diff --git a/slides/internals/collection-add-add-handler.xml b/slides/internals/collection-add-add-handler.xml new file mode 100644 index 00000000..37326824 --- /dev/null +++ b/slides/internals/collection-add-add-handler.xml @@ -0,0 +1,37 @@ +<slide> +<title>Collection: Add Write Handler</title> + +<blurb>%Zend/zend_object_handlers.c%</blurb> +<example class="small"><![CDATA[ +@@ -26,6 +26,7 @@ + #include "zend_objects_API.h" + #include "zend_object_handlers.h" + #include "zend_interfaces.h" ++#include "zend_collection.h" + #include "zend_exceptions.h" +… + +@@ -1042,6 +1045,8 @@ ZEND_API void zend_std_write_dimension(zend_object *object, zval *offset, zval * + zend_call_known_instance_method_with_2_params(funcs->zf_offsetset, object, NULL, &tmp_offset, value); + OBJ_RELEASE(object); + zval_ptr_dtor(&tmp_offset); ++ } else if (zend_class_implements_interface(ce, zend_ce_collection)) { ++ zend_collection_add_item(object, offset, value); + } else { + zend_bad_array_access(ce); + } +]]></example> + +<div effect="fade-in"> +<break/> +<blurb>%Zend/zend_collection.h%</blurb> +<example class="small"><![CDATA[ +@@ -34,6 +34,11 @@ void zend_collection_add_interfaces(zend_class_entry *ce); + void zend_collection_register_handlers(zend_class_entry *ce); + void zend_collection_register_props(zend_class_entry *ce); + ++void zend_collection_add_item(zend_object *object, zval *offset, zval *value); +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-add-ast-clean-up.xml b/slides/internals/collection-add-ast-clean-up.xml new file mode 100644 index 00000000..40c24451 --- /dev/null +++ b/slides/internals/collection-add-ast-clean-up.xml @@ -0,0 +1,19 @@ +<slide> +<title>Collection: Clean Up Class Entry</title> + +<blurb>%Zend/zend_opcode.c%</blurb> +<example class="small"><![CDATA[ +--- Zend/zend_opcode.c ++++ Zend/zend_opcode.c +@@ -430,6 +430,9 @@ ZEND_API void destroy_zend_class(zval *zv) + if (ce->backed_enum_table) { + zend_hash_release(ce->backed_enum_table); + } ++ if (ZEND_TYPE_IS_SET(ce->collection_item_type)) { ++ zend_type_release(ce->collection_item_type, 0); ++ } + if (ce->default_properties_table) { + zval *p = ce->default_properties_table; + zval *end = p + ce->default_properties_count; +]]></example> +</slide> diff --git a/slides/internals/collection-add-ast-type-check.xml b/slides/internals/collection-add-ast-type-check.xml new file mode 100644 index 00000000..87d68914 --- /dev/null +++ b/slides/internals/collection-add-ast-type-check.xml @@ -0,0 +1,45 @@ +<slide> +<title>Collection: Add AST Code</title> + +<blurb>%Zend/zend_compile.c%</blurb> +<example class="small"><![CDATA[ ++static void zend_compile_collection_key_type(zend_class_entry *ce, zend_ast *collection_key_type_ast) ++{ ++ ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); ++ zend_type type = zend_compile_typename(collection_key_type_ast, 0); ++ uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); ++ if (ZEND_TYPE_IS_COMPLEX(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) { ++ zend_string *type_string = zend_type_to_string(type); ++ zend_error_noreturn(E_COMPILE_ERROR, ++ "Collection key type must be int or string, %s given", ++ ZSTR_VAL(type_string)); ++ } ++ if (type_mask == MAY_BE_LONG) { ++ ce->collection_key_type = IS_LONG; ++ } else { ++ ZEND_ASSERT(type_mask == MAY_BE_STRING); ++ ce->collection_key_type = IS_STRING; ++ } ++ zend_type_release(type, 0); ++}]]></example> + +<div effect="fade-in"> +<example class="small"><![CDATA[ ++static void zend_compile_collection_item_type(zend_class_entry *ce, zend_ast *collection_item_type_ast) ++{ ++ ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); ++ zend_type type = zend_compile_typename(collection_item_type_ast, 0); ++ ++ if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_NEVER|MAY_BE_CALLABLE)) { ++ zend_string *str = zend_type_to_string(type); ++ zend_error_noreturn(E_COMPILE_ERROR, ++ "Collection item type cannot have type %s", ZSTR_VAL(str)); ++ } ++ ++ ce->collection_item_type = type; ++} ++ + static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ +]]></example> +</div> +</slide> diff --git a/slides/internals/collection-add-ast.xml b/slides/internals/collection-add-ast.xml new file mode 100644 index 00000000..1199f9d3 --- /dev/null +++ b/slides/internals/collection-add-ast.xml @@ -0,0 +1,44 @@ +<slide> +<title>Collection: Add AST Code</title> + +<blurb>%Zend/zend.h%</blurb> +<example class="small"><![CDATA[ + HashTable *backed_enum_table; + ++ uint32_t collection_key_type; ++ zend_type collection_item_type; +]]></example> + +<blurb>%Zend/zend_compile.c%</blurb> +<example class="small"><![CDATA[ + static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ + { + zend_ast_decl *decl = (zend_ast_decl *) ast; + zend_ast *extends_ast = decl->child[0]; + zend_ast *implements_ast = decl->child[1]; + zend_ast *stmt_ast = decl->child[2]; ++ zend_ast *collection_key_type_ast = decl->child[3]; ++ zend_ast *collection_item_type_ast = decl->child[4]; + zend_ast *enum_backing_type_ast = decl->child[4]; + zend_string *name, *lcname; + zend_class_entry *ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry)); +@@ -8003,7 +8039,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) + + CG(active_class_entry) = ce; + +- if (decl->child[3]) { ++ if (decl->child[3] && !(ce->ce_flags & ZEND_ACC_COLLECTION)) { + zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); + } + +@@ -8019,6 +8055,13 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) + zend_enum_register_props(ce); + } + ++ if (ce->ce_flags & ZEND_ACC_COLLECTION) { ++ zend_compile_collection_key_type(ce, collection_key_type_ast); ++ zend_compile_collection_item_type(ce, collection_item_type_ast); ++ } ++ +]]></example> +</slide> diff --git a/slides/internals/collection-add-has-handler.xml b/slides/internals/collection-add-has-handler.xml new file mode 100644 index 00000000..c2b5c5df --- /dev/null +++ b/slides/internals/collection-add-has-handler.xml @@ -0,0 +1,50 @@ +<slide> +<title>Collection: Add Has Handler</title> + +<blurb>%Zend/zend_object_handlers.c%</blurb> +<example class="small"><![CDATA[ +@@ -1068,6 +1073,8 @@ ZEND_API int zend_std_has_dimension(zend_object *object, zval *offset, int check + } + OBJ_RELEASE(object); + zval_ptr_dtor(&tmp_offset); ++ } else if (zend_class_implements_interface(ce, zend_ce_collection)) { ++ return zend_collection_has_item(object, offset); + } else { + zend_bad_array_access(ce); + return 0; +]]></example> + +<div effect="fade-in"> +<blurb>%Zend/zend_collection.h%</blurb> +<example class="small"><![CDATA[ + void zend_collection_add_item(zend_object *object, zval *offset, zval *value); ++int zend_collection_has_item(zend_object *object, zval *offset); +]]></example> +</div> + +<div effect="fade-in"> +<blurb>%Zend/zend_collection.c%</blurb> +<example class="small"><![CDATA[ +int zend_collection_has_item(zend_object *object, zval *offset) +{ + zval rv; + zval *value_prop; + zend_class_entry *ce = object->ce; + + if (!key_type_allowed(ce, offset)) { + return false; + } + + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + if (Z_TYPE_P(offset) == IS_STRING) { + return zend_hash_find(HASH_OF(value_prop), Z_STR_P(offset)) != NULL; + } else { + return zend_hash_index_find(HASH_OF(value_prop), Z_LVAL_P(offset)) != NULL; + } + + return false; +} +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-add-init-and-property.xml b/slides/internals/collection-add-init-and-property.xml new file mode 100644 index 00000000..5688deda --- /dev/null +++ b/slides/internals/collection-add-init-and-property.xml @@ -0,0 +1,60 @@ +<slide> +<title>Collection: Init and Storage</title> +<!-- +<blurb>%zend_compile.c%</blurb> +<example class="small"><![CDATA[ + zend_compile_collection_key_type(ce, collection_key_type_ast); + zend_compile_collection_item_type(ce, collection_item_type_ast); ++ zend_collection_register_handlers(ce); ++ zend_collection_register_props(ce); + } + +]]></example> +--> +<div effect="fade-in"> +<blurb>%Zend/zend_collection.h%</blurb> +<example class="small"><![CDATA[ +#ifndef ZEND_COLLECTION_H +#define ZEND_COLLECTION_H + +#include "zend.h" +#include <stdint.h> + +BEGIN_EXTERN_C() + +extern ZEND_API zend_object_handlers zend_collection_object_handlers; + +void zend_collection_register_handlers(zend_class_entry *ce); +void zend_collection_register_props(zend_class_entry *ce); + +END_EXTERN_C() +#endif /* ZEND_COLLECTION_H */ +]]></example> +</div> + +<div effect="fade-in"> +<blurb>%Zend/zend_collection.c%</blurb> +<example class="small"><![CDATA[ +#include "zend.h" +#include "zend_API.h" + +void zend_collection_register_handlers(zend_class_entry *ce) +{ + memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_collection_object_handlers.clone_obj = NULL; + zend_collection_object_handlers.compare = zend_objects_not_comparable; + ce->default_object_handlers = &zend_collection_object_handlers; +} + +void zend_collection_register_props(zend_class_entry *ce) +{ + zval name_default_value; + ZVAL_UNDEF(&name_default_value); + zend_type name_type = ZEND_TYPE_INIT_CODE(IS_ARRAY, 0, 0); + zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &name_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_R + ce->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; +} +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-add-interface-handlers.xml b/slides/internals/collection-add-interface-handlers.xml new file mode 100644 index 00000000..4c20df07 --- /dev/null +++ b/slides/internals/collection-add-interface-handlers.xml @@ -0,0 +1,57 @@ +<slide> +<title>Collection: Add Interface Handlers</title> + +<blurb>%Zend/zend_collection.c%</blurb> + +<div effect="fade-in"> +<example class="small"><![CDATA[ +static int zend_implement_collection(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->ce_flags & ZEND_ACC_COLLECTION) { + return SUCCESS; + } + + zend_error_noreturn(E_ERROR, "Non-collection class %s cannot implement interface %s", + ZSTR_VAL(class_type->name), + ZSTR_VAL(interface->name)); + + return FAILURE; +} +]]></example> +</div> + +<div effect="fade-in"> +<example class="small"><![CDATA[ +void zend_register_collection_ce(void) +{ + zend_ce_collection = register_class_Collection(); + zend_ce_collection->interface_gets_implemented = zend_implement_collection; + + memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_collection_object_handlers.clone_obj = NULL; + zend_collection_object_handlers.compare = zend_objects_not_comparable; +} +]]></example> +</div> + +<div effect="fade-in"> +<example class="small"><![CDATA[ +void zend_collection_add_interfaces(zend_class_entry *ce) +{ + uint32_t num_interfaces_before = ce->num_interfaces; + + ce->num_interfaces++; + + ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)); + + ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); + + ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_collection->name); + ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("collection", 0); + + ce->default_object_handlers = &zend_collection_object_handlers; +} +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-add-interface.xml b/slides/internals/collection-add-interface.xml new file mode 100644 index 00000000..f5155ef0 --- /dev/null +++ b/slides/internals/collection-add-interface.xml @@ -0,0 +1,34 @@ +<slide> +<title>Collection: Add Interface</title> + +<blurb>%Zend/zend_collection.stub.php%</blurb> +<example class="small"><![CDATA[ +<?php +/** @generate-class-entries */ +interface Collection +{ +} +]]></example> + +<blurb>%Zend/zend_collection.h%</blurb> +<example class="small"><![CDATA[ + extern ZEND_API zend_object_handlers zend_collection_object_handlers; ++extern ZEND_API zend_class_entry *zend_ce_collection; ++ ++void zend_register_collection_ce(void); ++void zend_collection_add_interfaces(zend_class_entry *ce); +]]></example> + +<div effect="fade-in"> +<break/> +<blurb>%Zend/zend_collection.c%</blurb> +<example class="small"><![CDATA[ ++#include "zend_collection_arginfo.h" ++#include "zend_execute.h" + ++ZEND_API zend_class_entry *zend_ce_collection; + ZEND_API zend_object_handlers zend_collection_object_handlers; +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-add-parser-rules.xml b/slides/internals/collection-add-parser-rules.xml new file mode 100644 index 00000000..ed499fd4 --- /dev/null +++ b/slides/internals/collection-add-parser-rules.xml @@ -0,0 +1,43 @@ +<slide> +<title>Collection: Add Parser Rules</title> + +<blurb>%Zend/zend_language_parser.y%</blurb> +<example class="small"><![CDATA[ +%type <ast> enum_declaration_statement enum_backing_type enum_case enum_case_expr +… + +enum_declaration_statement: + T_ENUM { $<num>$ = CG(zend_lineno); } + T_STRING enum_backing_type implements_list backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM|ZEND_ACC_FINAL, $<num>2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); } +;]]></example> + +<blurb>Add:</blurb> +<example class="small"><![CDATA[ +%type <ast> collection_declaration_statement collection_type +… + +collection_declaration_statement: + T_COLLECTION { $<num>$ = CG(zend_lineno); } + T_STRING '(' collection_type T_DOUBLE_ARROW collection_type ')' backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $<num>2, $9, zend_ast_get_str($3), NULL, NULL, $11, $5, $7); } +; + +collection_type: + type_expr { $$ = $1; } +… + + | enum_declaration_statement { $$ = $1; } + | collection_declaration_statement { $$ = $1; } +]]></example> + + +<blurb>%Zend/zend_compile.h%</blurb> + +<example class="small"><![CDATA[ +/* Special class types | | | */ +… +#define ZEND_ACC_ENUM (1 << 28) /* X | | | */ +#define ZEND_ACC_COLLECTION (1 << 30) /* X | | | */ +]]></example> +</slide> diff --git a/slides/internals/collection-add-read-handler.xml b/slides/internals/collection-add-read-handler.xml new file mode 100644 index 00000000..0f7e9f46 --- /dev/null +++ b/slides/internals/collection-add-read-handler.xml @@ -0,0 +1,26 @@ +<slide> +<title>Collection: Add Read Handler [1]</title> + +<blurb>%Zend/zend_object_handlers.c%</blurb> +<example class="small"><![CDATA[ +@@ -1019,6 +1020,8 @@ ZEND_API zval *zend_std_read_dimension(zend_object *object, zval *offset, int ty + return NULL; + } + return rv; ++ } else if (zend_class_implements_interface(ce, zend_ce_collection)) { ++ return zend_collection_read_item(object, offset); + } else { + zend_bad_array_access(ce); + return NULL; +]]></example> + +<div effect="fade-in"> +<break/> +<blurb>%Zend/zend_collection.h%</blurb> +<example class="small"><![CDATA[ + void zend_collection_add_item(zend_object *object, zval *offset, zval *value); ++zval *zend_collection_read_item(zend_object *object, zval *offset); +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-add-read-handler2.xml b/slides/internals/collection-add-read-handler2.xml new file mode 100644 index 00000000..d5020709 --- /dev/null +++ b/slides/internals/collection-add-read-handler2.xml @@ -0,0 +1,44 @@ +<slide> +<title>Collection: Add Read Handler [2]</title> + +<blurb>%Zend/zend_collection.c%</blurb> +<example class="small"><![CDATA[ +static int key_type_allowed(zend_class_entry *ce, zval *offset) +{ + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + + if (ce->collection_key_type != Z_TYPE_P(offset)) { + zend_type_error( + "Key type %s of element does not match collection key type %s", + offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), + zend_get_type_by_const(ce->collection_key_type) + ); + return false; + } + + return true; +} + +zval *zend_collection_read_item(zend_object *object, zval *offset) +{ + zval rv; + zval *value_prop, *value; + zend_class_entry *ce = object->ce; + + if (!key_type_allowed(ce, offset)) { + return NULL; + } + + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + + if (Z_TYPE_P(offset) == IS_STRING) { + value = zend_hash_find(HASH_OF(value_prop), Z_STR_P(offset)); + } else { + value = zend_hash_index_find(HASH_OF(value_prop), Z_LVAL_P(offset)); + } + + return value; +} +]]></example> + +</slide> diff --git a/slides/internals/collection-add-token.xml b/slides/internals/collection-add-token.xml new file mode 100644 index 00000000..4179d812 --- /dev/null +++ b/slides/internals/collection-add-token.xml @@ -0,0 +1,31 @@ +<slide> +<title>Collection: Add Token</title> + +<blurb>%Zend/zend_language_parser.y%</blurb> +<example inline="2"><![CDATA[ + %token <ident> T_ENUM "'enum'" +*+%token <ident> T_COLLECTION "'collection'"* + %token <ident> T_EXTENDS "'extends'" +]]></example> + +<blurb>%Zend/zend_language_scanner.l%</blurb> + +<example><![CDATA[ + RETURN_TOKEN_WITH_IDENT(T_ENUM); + } + ++/* ++ * The collection keyword must be followed by whitespace and another identifier. ++ * This avoids the BC break of using collection in classes, namespaces, functions and constants. ++ */ ++<ST_IN_SCRIPTING>"collection"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { ++ yyless(10); ++ RETURN_TOKEN_WITH_STR(T_STRING, 0); ++} ++<ST_IN_SCRIPTING>"collection"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { ++ yyless(10); ++ RETURN_TOKEN_WITH_IDENT(T_COLLECTION); ++} ++ +]]></example> +</slide> diff --git a/slides/internals/collection-add-unset-handler.xml b/slides/internals/collection-add-unset-handler.xml new file mode 100644 index 00000000..5ff71f0c --- /dev/null +++ b/slides/internals/collection-add-unset-handler.xml @@ -0,0 +1,49 @@ +<slide> +<title>Collection: Add Unset Handler</title> + +<blurb>%Zend/zend_object_handlers.c%</blurb> +<example class="small"><![CDATA[ +@@ -1251,6 +1258,8 @@ ZEND_API void zend_std_unset_dimension(zend_object *object, zval *offset) /* {{{ + zend_call_known_instance_method_with_1_params(funcs->zf_offsetunset, object, NULL, &tmp_offset); + OBJ_RELEASE(object); + zval_ptr_dtor(&tmp_offset); ++ } else if (zend_class_implements_interface(ce, zend_ce_collection)) { ++ zend_collection_unset_item(object, offset); + } else { + zend_bad_array_access(ce); + } +]]></example> + +<div effect="fade-in"> +<blurb>%Zend/zend_collection.h%</blurb> +<example class="small"><![CDATA[ + zval *zend_collection_read_item(zend_object *object, zval *offset); ++void zend_collection_unset_item(zend_object *object, zval *offset); +]]></example> +</div> + +<div effect="fade-in"> +<blurb>%Zend/zend_collection.c%</blurb> +<example class="small"><![CDATA[ +void zend_collection_unset_item(zend_object *object, zval *offset) +{ + zval rv; + zval *value_prop; + zend_class_entry *ce = object->ce; + + if (!key_type_allowed(ce, offset)) { + return; + } + + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + + if (Z_TYPE_P(offset) == IS_STRING) { + zend_hash_del(HASH_OF(value_prop), Z_STR_P(offset)); + } else { + zend_hash_index_del(HASH_OF(value_prop), Z_LVAL_P(offset)); + } +} +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-hook-init-and-property.xml b/slides/internals/collection-hook-init-and-property.xml new file mode 100644 index 00000000..15dbaffa --- /dev/null +++ b/slides/internals/collection-hook-init-and-property.xml @@ -0,0 +1,41 @@ +<slide> +<title>Collection: Hook-Up Init and Storage</title> + +<blurb>%zend_compile.c%</blurb> +<example class="small"><![CDATA[ + zend_compile_collection_key_type(ce, collection_key_type_ast); + zend_compile_collection_item_type(ce, collection_item_type_ast); ++ zend_collection_register_handlers(ce); ++ zend_collection_register_props(ce); + } + +]]></example> + +<div effect="fade-in"> +<break/> +<blurb>%configure.ac%</blurb> +<example class="small"><![CDATA[ +@@ -1722,7 +1722,7 @@ PHP_ADD_SOURCES(Zend, \ + zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \ + zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \ + zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \ +- zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c \ ++ zend_observer.c zend_system_id.c zend_enum.c zend_collection.c zend_fibers.c zend_atomic.c \ + zend_max_execution_timer.c \ +]]></example> +</div> + +<div effect="fade-in"> +<break/> +<blurb>%win32/build/config.w32%</blurb> +<example class="small"><![CDATA[ +@@ -240,7 +240,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ + zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \ + zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ + zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ +- zend_enum.c zend_fibers.c zend_atomic.c"); ++ zend_enum.c zend_collection.c zend_fibers.c zend_atomic.c"); +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-make-check-type-public.xml b/slides/internals/collection-make-check-type-public.xml new file mode 100644 index 00000000..ef2f0a5c --- /dev/null +++ b/slides/internals/collection-make-check-type-public.xml @@ -0,0 +1,30 @@ +<slide> +<title>Collection: Make Check Type Public</title> + +<blurb>%Zend/zend_execute.h%</blurb> +<example class="small"><![CDATA[ +@@ -89,6 +89,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_invalid_class_constant_type_error(uin + ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_property_error(const zend_property_inf + + ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg); ++ZEND_API bool zend_check_type(zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal); + ZEND_API ZEND_COLD void zend_verify_arg_error( + const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value); + ZEND_API ZEND_COLD void zend_verify_return_error( +]]></example> + + +<blurb>%Zend/zend_execute.c%</blurb> +<example class="small"><![CDATA[ +@@ -1182,7 +1182,7 @@ static zend_always_inline bool zend_check_type_slow( + * because this case is already checked at compile-time. */ + } + +-static zend_always_inline bool zend_check_type( ++zend_always_inline bool zend_check_type( + zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope, + bool is_return_type, bool is_internal) + { +]]></example> + +</slide> diff --git a/slides/internals/collection-setup-interface-handlers.xml b/slides/internals/collection-setup-interface-handlers.xml new file mode 100644 index 00000000..cec7b773 --- /dev/null +++ b/slides/internals/collection-setup-interface-handlers.xml @@ -0,0 +1,29 @@ +<slide> +<title>Collection: Add Interface Handlers</title> + +<blurb>%Zend/zend_compile.c%</blurb> +<example class="small"><![CDATA[ +@@ -8059,6 +8059,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) + if (ce->ce_flags & ZEND_ACC_COLLECTION) { + zend_compile_collection_key_type(ce, collection_key_type_ast); + zend_compile_collection_item_type(ce, collection_item_type_ast); ++ zend_collection_add_interfaces(ce); + zend_collection_register_handlers(ce); + zend_collection_register_props(ce); + } +]]></example> + +<div effect="fade-in"> +<break/> +<blurb>%Zend/zend_default_classes.c%</blurb> +<example class="small"><![CDATA[ +@@ -40,4 +40,5 @@ ZEND_API void zend_register_default_classes(void) + zend_register_attribute_ce(); + zend_register_enum_ce(); + zend_register_fiber_ce(); ++ zend_register_collection_ce(); + } +]]></example> +</div> + +</slide> diff --git a/slides/internals/collection-syntax-example.xml b/slides/internals/collection-syntax-example.xml new file mode 100644 index 00000000..8b8fa9b3 --- /dev/null +++ b/slides/internals/collection-syntax-example.xml @@ -0,0 +1,78 @@ +<slide> +<title>Collection: Syntax</title> + +<div effect="fade-out"> +<example inline="2"><![CDATA[<?php +*collection* Articles(*int|string* => *Class*) +{ +} +?>]]></example> +</div> + +<div effect="fade-in-out"> +<blurb>Example Usage:</blurb> +<example><![CDATA[<?php +collection Articles(string => Article) +{ +} + +class Article +{ + function __construct(public string $title) + { + } +} + +$c = new Articles; + +// OK: +$c["nine"] = new Article("The Ninth Planet: Pluto"); + +// Error: +$c["six"] = "Henry VIII"; +?>]]></example> + +</div> + +<div effect="fade-in"> +<blurb>%Zend/tests/collection/collection_3.phpt%</blurb> +<example class="small"><![CDATA[--TESTS-- +Collections: Syntax +--FILE-- +<?php +collection Articles(string => Article) { } + +class Article +{ + function __construct(public string $title) { } +} + +$c = new Articles; + +// OK: +$c["nine"] = new Article("The Ninth Planet: Pluto"); +print_r($c); + +// Error: +try { + $c["six"] = "Henry VIII"; +} catch (Error $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Articles Object +( + [value] => Array + ( + [nine] => Article Object + ( + [title] => The Ninth Planet: Pluto + ) + ) +) +TypeError: Value type string does not match collection item type Article +]]></example> + +</div> +</slide> diff --git a/slides/internals/recap-content.xml b/slides/internals/recap-content.xml index 4f72b290..e440c71d 100644 --- a/slides/internals/recap-content.xml +++ b/slides/internals/recap-content.xml @@ -5,6 +5,7 @@ <bullet>Stages:<br/>Code → Tokens → Parsing → AST → Byte Code</bullet> <bullet>All looping structures are jumps</bullet> <bullet>Code analysis for Fun and Profit</bullet> + <bullet>Collections branch with each individual step:<br/>%https://github.com/derickr/php-src/tree/collections%</bullet> </list> <break/> diff --git a/slides/mongodb/me.xml b/slides/mongodb/me.xml index 2be5addc..bb6531f2 100644 --- a/slides/mongodb/me.xml +++ b/slides/mongodb/me.xml @@ -13,6 +13,6 @@ <!-- <bullet>Host of *https://phpinternals.news* podcast</bullet> --> <bullet>I ♥🌍 maps, I ♥🍺 beer, I ♥🥃 whisky</bullet> <bullet>mastodon: :-:mastodon:-:</bullet> - <bullet>|777777|twitter: @:-:twitter:-:|</bullet> + <bullet>|999999|twitter: @:-:twitter:-:|</bullet> </list> </slide>