diff --git a/depend b/depend index b941d1490e3238..1392a36c3e9809 100644 --- a/depend +++ b/depend @@ -7809,6 +7809,7 @@ jit.$(OBJEXT): {$(VPATH)}internal/error.h jit.$(OBJEXT): {$(VPATH)}internal/eval.h jit.$(OBJEXT): {$(VPATH)}internal/event.h jit.$(OBJEXT): {$(VPATH)}internal/fl_type.h +jit.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h jit.$(OBJEXT): {$(VPATH)}internal/gc.h jit.$(OBJEXT): {$(VPATH)}internal/glob.h jit.$(OBJEXT): {$(VPATH)}internal/globals.h diff --git a/jit.c b/jit.c index 2d3145e3f3a366..caca49d39dfaec 100644 --- a/jit.c +++ b/jit.c @@ -18,6 +18,7 @@ #include "internal/string.h" #include "internal/class.h" #include "internal/imemo.h" +#include "ruby/internal/core/rtypeddata.h" enum jit_bindgen_constants { // Field offsets for the RObject struct @@ -27,6 +28,9 @@ enum jit_bindgen_constants { // Field offset for prime classext's fields_obj from a class pointer RCLASS_OFFSET_PRIME_FIELDS_OBJ = offsetof(struct RClass_and_rb_classext_t, classext.fields_obj), + // Field offset for fields_obj in RTypedData + RTYPEDDATA_OFFSET_FIELDS_OBJ = offsetof(struct RTypedData, fields_obj), + // Field offsets for the RString struct RUBY_OFFSET_RSTRING_LEN = offsetof(struct RString, len), @@ -541,6 +545,13 @@ rb_jit_class_fields_embedded_p(VALUE klass) return !fields_obj || !FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP); } +bool +rb_jit_typed_data_fields_embedded_p(VALUE obj) +{ + VALUE fields_obj = RTYPEDDATA(obj)->fields_obj; + return !fields_obj || !FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP); +} + // Acquire the VM lock and then signal all other Ruby threads (ractors) to // contend for the VM lock, putting them to sleep. ZJIT and YJIT use this to // evict threads running inside generated code so among other things, it can diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index c6bb6e5f1f453e..4898fdd435c4b6 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -938,6 +938,78 @@ def failure? !success? end + # Returns true if the parsed source is an incomplete expression that could + # become valid with additional input. This is useful for REPL contexts (such + # as IRB) where the user may be entering a multi-line expression one line at + # a time and the implementation needs to determine whether to wait for more + # input or to evaluate what has been entered so far. + # + # Concretely, this returns true when every error present is caused by the + # parser reaching the end of the input before a construct was closed (e.g. + # an unclosed string, array, block, or keyword), and returns false when any + # error is caused by a token that makes the input structurally invalid + # regardless of what might follow (e.g. a stray `end`, `]`, or `)` with no + # matching opener). + # + # Examples: + # + # Prism.parse("1 + [").continuable? #=> true (unclosed array) + # Prism.parse("1 + ]").continuable? #=> false (stray ]) + # Prism.parse("tap do").continuable? #=> true (unclosed block) + # Prism.parse("end.tap do").continuable? #=> false (stray end) + # + #-- + #: () -> bool + def continuable? + return false if errors.empty? + + offset = source.source.bytesize + errors.all? { |error| CONTINUABLE.include?(error.type) || error.location.start_offset == offset } + end + + # The set of error types whose location the parser places at the opening + # token of an unclosed construct rather than at the end of the source. These + # errors always indicate incomplete input regardless of their byte position, + # so they are checked by type rather than by location. + #-- + #: Array[Symbol] + CONTINUABLE = %i[ + begin_term + begin_upcase_term + block_param_pipe_term + block_term_brace + block_term_end + case_missing_conditions + case_term + class_term + conditional_term + conditional_term_else + def_term + embdoc_term + end_upcase_term + for_term + hash_term + heredoc_term + lambda_term_brace + lambda_term_end + list_i_lower_term + list_i_upper_term + list_w_lower_term + list_w_upper_term + module_term + regexp_term + rescue_term + string_interpolated_term + string_literal_eof + symbol_term_dynamic + symbol_term_interpolated + until_term + while_term + xstring_term + ].freeze + + private_constant :CONTINUABLE + # Create a code units cache for the given encoding. #-- #: (Encoding encoding) -> _CodeUnitsCache diff --git a/prism/config.yml b/prism/config.yml index dbf0c9e1e1952d..d8a10bc11310b7 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -64,6 +64,7 @@ errors: - DEF_ENDLESS - DEF_ENDLESS_PARAMETERS - DEF_ENDLESS_SETTER + - DEF_ENDLESS_DO_BLOCK - DEF_NAME - DEF_PARAMS_TERM - DEF_PARAMS_TERM_PAREN diff --git a/prism/prism.c b/prism/prism.c index 2603bf7adb74c4..c644c947538c4e 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -18998,6 +18998,20 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_DEFINED + 1, allow_command_call, false, PM_ERR_DEF_ENDLESS, (uint16_t) (depth + 1)); + // In an endless method definition, the body is not allowed to + // be a command with a do..end block. + if (PM_NODE_TYPE_P(statement, PM_CALL_NODE)) { + pm_call_node_t *call = (pm_call_node_t *) statement; + + if (call->arguments != NULL && call->block != NULL && PM_NODE_TYPE_P(call->block, PM_BLOCK_NODE)) { + pm_block_node_t *block = (pm_block_node_t *) call->block; + + if (parser->start[block->opening_loc.start] != '{') { + pm_parser_err_node(parser, call->block, PM_ERR_DEF_ENDLESS_DO_BLOCK); + } + } + } + if (accept1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index c943c3afb95534..d717dc1e16e79f 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -148,6 +148,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS_PARAMETERS] = { "could not parse the endless method parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS_DO_BLOCK] = { "unexpected `do` for block in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_NAME] = { "unexpected %s; expected a method name", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "unexpected %s; expected a `)` to close the parameters", PM_ERROR_LEVEL_SYNTAX }, diff --git a/test/prism/errors/def_endless_do.txt b/test/prism/errors/def_endless_do.txt new file mode 100644 index 00000000000000..4d786638a685b9 --- /dev/null +++ b/test/prism/errors/def_endless_do.txt @@ -0,0 +1,3 @@ +def a = a b do 1 end + ^~~~~~~~ unexpected `do` for block in an endless method definition + diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 27610e89d38ad5..898f4afb45f20f 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -109,6 +109,41 @@ def test_unclosed_heredoc_and_interpolation assert_nil(statement.parts[0].statements) end + def test_continuable + # Valid input is not continuable (nothing to continue). + refute_predicate Prism.parse("1 + 1"), :continuable? + refute_predicate Prism.parse(""), :continuable? + + # Stray closing tokens make input non-continuable regardless of what + # follows (matches the feature-request examples exactly). + refute_predicate Prism.parse("1 + ]"), :continuable? + refute_predicate Prism.parse("end.tap do"), :continuable? + + # Unclosed constructs are continuable. + assert_predicate Prism.parse("1 + ["), :continuable? + assert_predicate Prism.parse("tap do"), :continuable? + + # Unclosed keywords. + assert_predicate Prism.parse("def foo"), :continuable? + assert_predicate Prism.parse("class Foo"), :continuable? + assert_predicate Prism.parse("module Foo"), :continuable? + assert_predicate Prism.parse("if true"), :continuable? + assert_predicate Prism.parse("while true"), :continuable? + assert_predicate Prism.parse("begin"), :continuable? + assert_predicate Prism.parse("for x in [1]"), :continuable? + + # Unclosed delimiters. + assert_predicate Prism.parse("{"), :continuable? + assert_predicate Prism.parse("foo("), :continuable? + assert_predicate Prism.parse('"hello'), :continuable? + assert_predicate Prism.parse("'hello"), :continuable? + assert_predicate Prism.parse("<<~HEREDOC\nhello"), :continuable? + + # A mix: stray end plus an unclosed block is not continuable because the + # stray end cannot be fixed by appending more input. + refute_predicate Prism.parse("end\ntap do"), :continuable? + end + private def assert_errors(filepath, version) diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 919d1e18afaea0..88919ac8748f5d 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -988,6 +988,7 @@ pub type rb_seq_param_keyword_struct = pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RCLASS_OFFSET_PRIME_FIELDS_OBJ: jit_bindgen_constants = 40; +pub const RTYPEDDATA_OFFSET_FIELDS_OBJ: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; pub const RUBY_OFFSET_EC_CFP: jit_bindgen_constants = 16; pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: jit_bindgen_constants = 32; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 957c29dc154505..18436270d74ba2 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -81,6 +81,7 @@ fn main() { .allowlist_type("RBasic") .allowlist_type("ruby_rstring_flags") + .allowlist_type("rbimpl_typeddata_flags") // This function prints info about a value and is useful for debugging .allowlist_function("rb_raw_obj_info") @@ -310,6 +311,8 @@ fn main() { .allowlist_function("rb_jit_shape_too_complex_p") .allowlist_function("rb_jit_multi_ractor_p") .allowlist_function("rb_jit_class_fields_embedded_p") + .allowlist_function("rb_jit_typed_data_p") + .allowlist_function("rb_jit_typed_data_fields_embedded_p") .allowlist_function("rb_jit_vm_lock_then_barrier") .allowlist_function("rb_jit_vm_unlock") .allowlist_function("rb_jit_for_each_iseq") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 565dd65623ec93..1ca29d5bc3312e 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -94,6 +94,8 @@ use std::fmt::{Debug, Display, Formatter}; use std::os::raw::{c_char, c_int, c_uint}; use std::panic::{catch_unwind, UnwindSafe}; +use crate::cast::IntoUsize as _; + // We check that we can do this with the configure script and a couple of // static asserts. u64 and not usize to play nice with lowering to x86. pub type size_t = u64; @@ -602,6 +604,16 @@ impl VALUE { unsafe { rb_jit_class_fields_embedded_p(self) } } + pub fn typed_data_p(self) -> bool { + !self.special_const_p() && + self.builtin_type() == RUBY_T_DATA && + 0 != (self.builtin_flags() & RUBY_TYPED_FL_IS_TYPED_DATA.to_usize()) + } + + pub fn typed_data_fields_embedded_p(self) -> bool { + unsafe { rb_jit_typed_data_fields_embedded_p(self) } + } + pub fn as_fixnum(self) -> i64 { assert!(self.fixnum_p()); (self.0 as i64) >> 1 diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index b8e90fdf6f5b69..5bac9fdf19d1c7 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -341,6 +341,13 @@ pub const RMODULE_IS_REFINEMENT: ruby_rmodule_flags = 8192; pub type ruby_rmodule_flags = u32; pub const ROBJECT_HEAP: ruby_robject_flags = 65536; pub type ruby_robject_flags = u32; +pub const RUBY_TYPED_FREE_IMMEDIATELY: rbimpl_typeddata_flags = 1; +pub const RUBY_TYPED_EMBEDDABLE: rbimpl_typeddata_flags = 2; +pub const RUBY_TYPED_FROZEN_SHAREABLE: rbimpl_typeddata_flags = 256; +pub const RUBY_TYPED_WB_PROTECTED: rbimpl_typeddata_flags = 32; +pub const RUBY_TYPED_FL_IS_TYPED_DATA: rbimpl_typeddata_flags = 64; +pub const RUBY_TYPED_DECL_MARKING: rbimpl_typeddata_flags = 16384; +pub type rbimpl_typeddata_flags = u32; pub type rb_event_flag_t = u32; pub type rb_block_call_func = ::std::option::Option< unsafe extern "C" fn( @@ -1876,6 +1883,7 @@ pub type zjit_struct_offsets = u32; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RCLASS_OFFSET_PRIME_FIELDS_OBJ: jit_bindgen_constants = 40; +pub const RTYPEDDATA_OFFSET_FIELDS_OBJ: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; pub const RUBY_OFFSET_EC_CFP: jit_bindgen_constants = 16; pub const RUBY_OFFSET_EC_INTERRUPT_FLAG: jit_bindgen_constants = 32; @@ -2229,6 +2237,7 @@ unsafe extern "C" { pub fn rb_jit_shape_too_complex_p(shape_id: shape_id_t) -> bool; pub fn rb_jit_multi_ractor_p() -> bool; pub fn rb_jit_class_fields_embedded_p(klass: VALUE) -> bool; + pub fn rb_jit_typed_data_fields_embedded_p(obj: VALUE) -> bool; pub fn rb_jit_vm_lock_then_barrier( recursive_lock_level: *mut ::std::os::raw::c_uint, file: *const ::std::os::raw::c_char, diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 82b7c057cbb4d8..a88eee26d9101f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2699,7 +2699,9 @@ impl Function { let newly_reachable = reachable.insert($target.target); let mut target_changed = newly_reachable; for (idx, arg) in $target.args.iter().enumerate() { + let arg = self.union_find.borrow().find_const(*arg); let param = $self.blocks[$target.target.0].params[idx]; + let param = self.union_find.borrow().find_const(param); let new = self.insn_types[param.0].union(self.insn_types[arg.0]); if !self.insn_types[param.0].bit_equal(new) { self.insn_types[param.0] = new; @@ -2717,37 +2719,38 @@ impl Function { in_worklist.remove(block); if !reachable.get(block) { continue; } for insn_id in &self.blocks[block.0].insns { - let insn_type = match self.find(*insn_id) { - Insn::IfTrue { val, target } => { + let insn_id = self.union_find.borrow().find_const(*insn_id); + let insn_type = match &self.insns[insn_id.0] { + &Insn::IfTrue { val, ref target } => { assert!(!self.type_of(val).bit_equal(types::Empty)); if self.type_of(val).could_be(Type::from_cbool(true)) { enqueue!(self, target); } continue; } - Insn::IfFalse { val, target } => { + &Insn::IfFalse { val, ref target } => { assert!(!self.type_of(val).bit_equal(types::Empty)); if self.type_of(val).could_be(Type::from_cbool(false)) { enqueue!(self, target); } continue; } - Insn::Jump(target) => { + &Insn::Jump(ref target) => { enqueue!(self, target); continue; } - Insn::Entries { targets } => { - for target in &targets { + &Insn::Entries { ref targets } => { + for target in targets { if reachable.insert(*target) { worklist_add!(*target); } } continue; } - insn if insn.has_output() => self.infer_type(*insn_id), + insn if insn.has_output() => self.infer_type(insn_id), _ => continue, }; - if !self.type_of(*insn_id).bit_equal(insn_type) { + if !self.type_of(insn_id).bit_equal(insn_type) { self.insn_types[insn_id.0] = insn_type; } } @@ -3949,8 +3952,34 @@ impl Function { return_type: types::BasicObject, elidable: true }) } + } else if recv_type.flags().is_typed_data() { + // Typed T_DATA: load from fields_obj at fixed offset in RTypedData + let fields_obj = self.push_insn(block, Insn::LoadField { + recv: self_val, id: ID!(_fields_obj), + offset: RTYPEDDATA_OFFSET_FIELDS_OBJ as i32, + return_type: types::RubyValue, + }); + if recv_type.flags().is_fields_embedded() { + let offset = ROBJECT_OFFSET_AS_ARY as i32 + + (SIZEOF_VALUE * ivar_index.to_usize()) as i32; + self.push_insn(block, Insn::LoadField { + recv: fields_obj, id, offset, + return_type: types::BasicObject, + }) + } else { + let ptr = self.push_insn(block, Insn::LoadField { + recv: fields_obj, id: ID!(_as_heap), + offset: ROBJECT_OFFSET_AS_HEAP_FIELDS as i32, + return_type: types::CPtr, + }); + let offset = SIZEOF_VALUE_I32 * ivar_index as i32; + self.push_insn(block, Insn::LoadField { + recv: ptr, id, offset, + return_type: types::BasicObject, + }) + } } else if !recv_type.flags().is_t_object() { - // Non-T_OBJECT, non-class/module (e.g. T_DATA): fall back to C call + // Non-T_OBJECT, non-class/module, non-typed-data: fall back to C call // NOTE: it's fine to use rb_ivar_get_at_no_ractor_check because // getinstancevariable does assume_single_ractor_mode() let ivar_index_insn = self.push_insn(block, Insn::Const { val: Const::CUInt16(ivar_index as u16) }); @@ -5112,11 +5141,15 @@ impl Function { let mut num_in_edges = vec![0; self.blocks.len()]; for block in self.rpo() { for &insn in &self.blocks[block.0].insns { - match self.find(insn) { - Insn::IfTrue { target, .. } | Insn::IfFalse { target, .. } | Insn::Jump(target) => { - num_in_edges[target.target.0] += 1; + // Instructions without output, including branch instructions, can't be targets of + // make_equal_to, so we don't need find() here. + match &self.insns[insn.0] { + Insn::IfTrue { target: BranchEdge { target, .. }, .. } + | Insn::IfFalse { target: BranchEdge { target, .. }, .. } + | Insn::Jump(BranchEdge { target, .. }) => { + num_in_edges[target.0] += 1; } - Insn::Entries { ref targets } => { + Insn::Entries { targets } => { for target in targets { num_in_edges[target.0] += 1; } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 1a68242169d8f1..53f9e4a138f019 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -7253,7 +7253,8 @@ mod hir_opt_tests { } #[test] - fn test_optimize_getivar_on_t_data() { + fn test_optimize_getivar_on_t_struct() { + // Range is T_STRUCT (not T_DATA): falls back to CCall eval(" class C < Range def test = @a @@ -7285,6 +7286,78 @@ mod hir_opt_tests { "); } + #[test] + fn test_optimize_getivar_on_typed_data() { + // Thread is typed T_DATA: uses LoadField chain via RTypedData fields_obj + eval(" + class C < Thread + def test = @a + end + obj = C.new { } + obj.join + obj.instance_variable_set(:@a, 1) + obj.test + TEST = C.instance_method(:test) + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + PatchPoint SingleRactorMode + v17:HeapBasicObject = GuardType v6, HeapBasicObject + v18:CShape = LoadField v17, :_shape_id@0x1000 + v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) + v20:RubyValue = LoadField v17, :_fields_obj@0x1002 + v21:BasicObject = LoadField v20, :@a@0x1002 + CheckInterrupts + Return v21 + "); + } + + #[test] + fn test_optimize_getivar_on_typed_data_heap_fields() { + // Typed T_DATA with enough ivars to force heap field storage + eval(" + class C < Thread + def test = @var1000 + end + obj = C.new { } + obj.join + 1000.times { |i| obj.instance_variable_set(:\"@var#{i}\", 1) } + obj.instance_variable_set(:@var1000, 42) + obj.test + TEST = C.instance_method(:test) + "); + assert_snapshot!(hir_string_proc("TEST"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + PatchPoint SingleRactorMode + v17:HeapBasicObject = GuardType v6, HeapBasicObject + v18:CShape = LoadField v17, :_shape_id@0x1000 + v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) + v20:RubyValue = LoadField v17, :_fields_obj@0x1002 + v21:CPtr = LoadField v20, :_as_heap@0x1002 + v22:BasicObject = LoadField v21, :@var1000@0x1003 + CheckInterrupts + Return v22 + "); + } + #[test] fn test_optimize_getivar_on_module_multi_ractor() { eval(" diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 4ec79b2e4631ed..795aa6d60649e3 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -207,6 +207,8 @@ impl Flags { const IS_FIELDS_EMBEDDED: u32 = 1 << 5; /// Object is a T_CLASS or T_MODULE const IS_T_CLASS_OR_MODULE: u32 = 1 << 6; + /// Object is a typed T_DATA (RTYPEDDATA_P) + const IS_TYPED_DATA: u32 = 1 << 7; pub fn none() -> Self { Self(Self::NONE) } @@ -218,6 +220,7 @@ impl Flags { pub fn is_object_profiling(self) -> bool { (self.0 & Self::IS_OBJECT_PROFILING) != 0 } pub fn is_fields_embedded(self) -> bool { (self.0 & Self::IS_FIELDS_EMBEDDED) != 0 } pub fn is_t_class_or_module(self) -> bool { (self.0 & Self::IS_T_CLASS_OR_MODULE) != 0 } + pub fn is_typed_data(self) -> bool { (self.0 & Self::IS_TYPED_DATA) != 0 } } /// opt_send_without_block/opt_plus/... should store: @@ -300,6 +303,12 @@ impl ProfiledType { flags.0 |= Flags::IS_FIELDS_EMBEDDED; } } + if obj.typed_data_p() { + flags.0 |= Flags::IS_TYPED_DATA; + if obj.typed_data_fields_embedded_p() { + flags.0 |= Flags::IS_FIELDS_EMBEDDED; + } + } Self { class: obj.class_of(), shape: obj.shape_id_of(), flags } }