use MASTOps; my int $initial_bytecode_size := 128; # How much memory we reserve initially for a frame's bytecode my int $initial_annotations_size := 128; # How much memory we reserve initially for a frame's annotations my int $MVM_reg_void := 0; # not really a register; just a result/return kind marker my int $MVM_reg_int8 := 1; my int $MVM_reg_int16 := 2; my int $MVM_reg_int32 := 3; my int $MVM_reg_int64 := 4; my int $MVM_reg_num32 := 5; my int $MVM_reg_num64 := 6; my int $MVM_reg_str := 7; my int $MVM_reg_obj := 8; my int $MVM_reg_uint8 := 17; my int $MVM_reg_uint16 := 18; my int $MVM_reg_uint32 := 19; my int $MVM_reg_uint64 := 20; my int $MVM_operand_literal := 0; my int $MVM_operand_read_reg := 1; my int $MVM_operand_write_reg := 2; my int $MVM_operand_read_lex := 3; my int $MVM_operand_write_lex := 4; my int $MVM_operand_rw_mask := 7; my int $MVM_operand_int8 := ($MVM_reg_int8 * 8); my int $MVM_operand_int16 := ($MVM_reg_int16 * 8); my int $MVM_operand_int32 := ($MVM_reg_int32 * 8); my int $MVM_operand_int64 := ($MVM_reg_int64 * 8); my int $MVM_operand_num32 := ($MVM_reg_num32 * 8); my int $MVM_operand_num64 := ($MVM_reg_num64 * 8); my int $MVM_operand_str := ($MVM_reg_str * 8); my int $MVM_operand_obj := ($MVM_reg_obj * 8); my int $MVM_operand_ins := (9 * 8); my int $MVM_operand_type_var := (10 * 8); my int $MVM_operand_lex_outer := (11 * 8); my int $MVM_operand_coderef := (12 * 8); my int $MVM_operand_callsite := (13 * 8); my int $MVM_operand_type_mask := (31 * 8); my int $MVM_operand_uint8 := ($MVM_reg_uint8 * 8); my int $MVM_operand_uint16 := ($MVM_reg_uint16 * 8); my int $MVM_operand_uint32 := ($MVM_reg_uint32 * 8); my int $MVM_operand_uint64 := ($MVM_reg_uint64 * 8); class MAST::Bytecode is repr('VMArray') is array_type(uint8) { method new() { nqp::create(self) } method write_s(str $s) { nqp::encode($s, 'utf8', self); } method write_double(num $n) { nqp::writenum(self, nqp::elems(self), $n, 13); } method write_uint32(uint32 $i) { nqp::writeuint(self, nqp::elems(self), $i, 9); } method write_uint64(uint64 $i) { nqp::writeuint(self, nqp::elems(self), $i, 13); } method read_uint32_at(uint $pos) { nqp::readuint(self, $pos, 9) } method write_uint32_at(uint32 $i, uint $pos) { nqp::writeuint(self, $pos, $i, 9); } method write_uint16(uint16 $i) { nqp::writeuint(self, nqp::elems(self), $i, 5); } method write_uint16_at(uint16 $i, uint $pos) { nqp::writeuint(self, $pos, $i, 5); } method write_uint8(uint8 $i) { nqp::writeuint(self, nqp::elems(self), $i, 1); } method write_buf(@buf) { nqp::splice(self, @buf, nqp::elems(self), 0); } method write_buf_at(@buf, uint $offset) { nqp::splice(self, @buf, $offset, 0); } method dump() { note(nqp::elems(self) ~ " bytes"); for self { note($_); } } } my %uint_map; my %int_map; my %num_map; %int_map<8> := 1; %int_map<16> := 2; %int_map<32> := 3; %int_map<64> := 4; %num_map<32> := 5; %num_map<64> := 6; %uint_map<8> := 17; %uint_map<16> := 18; %uint_map<32> := 19; %uint_map<64> := 20; sub type_to_local_type($t) { my $spec := nqp::objprimspec($t); if $spec == 0 { 8 } elsif $spec == 1 { (nqp::objprimunsigned($t) ?? %uint_map !! %int_map){nqp::objprimbits($t)} } elsif $spec == 2 { %num_map{nqp::objprimbits($t)} } elsif $spec == 3 { 7 } elsif $spec == 10 { %uint_map{nqp::objprimbits($t)} } else { nqp::die("Unknwon local type: " ~ $t.HOW.name($t) ~ ": " ~ $spec); } } # MoarVM AST nodes # This file contains a set of nodes that are compiled into MoarVM # bytecode. These nodes constitute the official high-level interface # to the VM. At some point, the bytecode itself will be declared # official also. Note that no text-based mapping to/from these nodes # will ever be official, however. # Extension op name/signature registry; keeps track of all the known extension # ops and their signatures. class MAST::ExtOpRegistry { my %extop_sigs; # Registers an extension op, specifying a name and type expected types of # each of the operands. method register_extop($name, *@sig) { if nqp::existskey(%extop_sigs, $name) { nqp::die("MoarVM extension op '$name' already registered"); } my @sig_i := nqp::list_i(); for @sig { nqp::push_i(@sig_i, $_); } %extop_sigs{$name} := @sig_i; } # Checks if an extop is registered. method extop_known($name) { nqp::existskey(%extop_sigs, $name) } # Gets the signature of an extop, which we can rely on to be a list of # native integers. method extop_signature($name) { unless nqp::existskey(%extop_sigs, $name) { nqp::die("MoarVM extension op '$name' is not known"); } %extop_sigs{$name} } } # The extension of base number (everything below is internal). my int $EXTOP_BASE := 1024; # The base class for all nodes. class MAST::Node { method dump($indent = "") { my @lines := nqp::list(); self.dump_lines(@lines, $indent); nqp::join("\n", @lines); } method dump_lines(@lines, $indent) { nqp::push(@lines, $indent~"MAST::Node "); } } # Everything lives within a compilation unit. Note that this may # or may not map to a HLL notion of compilation unit; it is always # a set of things that we're going to compile "in one go". The # input to the AST to bytecode convertor should always be one of # these. class MAST::CompUnit is MAST::Node { # The set of frames that make up this compilation unit. has @!frames; # The HLL name. has str $!hll; # The unit's mainline frame. has $!mainline_frame; # The frame for the main entry point, if any. has $!main_frame; # The frame for the library-load entry point, if any. has $!load_frame; # The frame containing the deserialization code, if any. has $!deserialize_frame; # SC handles that we depend on. has @!sc_handles; # Mapping of SC handle names to indexes, for faster lookup. has %!sc_lookup; # List of extops that we are using. For each extop used in this compunit, # this list contains its signature. has @!extop_sigs; # Mapping of extop names to extop signature indexes (in the @!extop_sigs # array). has %!extop_idx; # String list of extop names. has @!extop_names; # Serialized data. has $!serialized; has $!string_heap; has $!writer; method BUILD( :$writer, :@frames = nqp::list, :@sc_handles = nqp::list, :%sc_lookup = nqp::hash, :@extop_sigs = nqp::list, :@extop_names = nqp::list, :%extop_idx = nqp::hash, ) { $!writer := $writer; @!frames := @frames; @!sc_handles := @sc_handles; %!sc_lookup := %sc_lookup; @!extop_sigs := @extop_sigs; @!extop_names := @extop_names; %!extop_idx := %extop_idx; } method writer() { $!writer } method add_frame($frame) { my int $idx := nqp::elems(@!frames); $frame.set_index($idx); nqp::push(@!frames, $frame); } method dump_lines(@lines, $indent) { nqp::push(@lines, $_.dump($indent)) for @!frames; } method hll($hll?) { nqp::defined($hll) ?? ($!hll := $hll) !! $!hll } method mainline_frame($frame?) { nqp::defined($frame) ?? ($!mainline_frame := $frame) !! $!mainline_frame } method main_frame($frame?) { nqp::defined($frame) ?? ($!main_frame := $frame) !! $!main_frame } method load_frame($frame?) { nqp::defined($frame) ?? ($!load_frame := $frame) !! $!load_frame } method deserialize_frame($frame?) { nqp::defined($frame) ?? ($!deserialize_frame := $frame) !! $!deserialize_frame } method serialized($serialized?) { nqp::defined($serialized) ?? ($!serialized := $serialized) !! $!serialized } method string_heap($string_heap?) { nqp::defined($string_heap) ?? ($!string_heap := $string_heap) !! $!string_heap } method add_strings(@strings) { my int $i := 1; my int $elems := nqp::unbox_i($!writer.string-heap.elems); for @strings { if ++$i > $elems { $!writer.add-string($_); } } } method sc_idx($sc) { my str $handle := nqp::scgethandle($sc); if nqp::existskey(%!sc_lookup, $handle) { nqp::atkey(%!sc_lookup, $handle) } else { my $id := nqp::elems(@!sc_handles); nqp::push(@!sc_handles, $handle); nqp::bindkey(%!sc_lookup, $handle, $id); $id } } # Gets the opcode for an extop in the current compilation unit. If this is # the first use of the extop, gives it an index for this compilation unit. method get_extop_code(str $name) { if nqp::existskey(%!extop_idx, $name) { %!extop_idx{$name} + $EXTOP_BASE } else { my int $idx := +@!extop_sigs; @!extop_names[$idx] := $name; @!extop_sigs[$idx] := MAST::ExtOpRegistry.extop_signature($name); %!extop_idx{$name} := $idx; $idx + $EXTOP_BASE } } } # Literal values. class MAST::SVal is MAST::Node { method new(:$value!) { $value } } class MAST::IVal is MAST::Node { method new(:$value!, :$size = 64, :$signed = 1) { $value } } class MAST::NVal is MAST::Node { method new(:$value!, :$size = 64) { $value } } # A local lookup. class MAST::Local is MAST::Node { has uint $!index is box_target; method new(:$index!) { my $obj := nqp::create(self); nqp::bindattr_i($obj, MAST::Local, '$!index', $index); $obj } method index() { $!index } method dump_lines(@lines, $indent) { nqp::push(@lines, $indent~"MAST::Local index<$!index>"); } } # A lexical lookup. class MAST::Lexical is MAST::Node { has int $!index; has int $!frames_out; method new(:$index!, :$frames_out = 0) { my $obj := nqp::create(self); nqp::bindattr_i($obj, MAST::Lexical, '$!index', $index); nqp::bindattr_i($obj, MAST::Lexical, '$!frames_out', $frames_out); $obj } method index() { $!index } method frames_out() { $!frames_out } method dump_lines(@lines, $indent) { nqp::push(@lines, $indent~"MAST::Lexical index<$!index>, frames_out<$!frames_out>"); } } # Argument flags. module Arg { our $obj := 1; our $int := 2; our $num := 4; our $str := 8; our $literal := 16; our $named := 32; our $flat := 64; our $uint := 128; } # Labels (used directly in the instruction stream indicates where the # label goes; can also be used as an instruction operand). class MAST::Label is MAST::Node { my int $cur_id := 0; my $cur_id_lock := NQPLock.new; has $!id; method new() { my $label := nqp::create(self); nqp::lock($cur_id_lock); $cur_id := $cur_id + 1; nqp::unlock($cur_id_lock); $label.set-id(~$cur_id); $*MAST_FRAME.keep-label($label); $label } method set-id($id) { $!id := $id; } method id() { $!id } method dump_lines(@lines, $indent) { my int $addr := nqp::where(self); nqp::push(@lines, $indent ~ "MAST::Label <$addr>"); } } # An operation to be executed. The operands must be either registers, # literals or labels (depending on what the instruction needs). class MAST::Op is MAST::Node { my %op_codes := MAST::Ops.WHO<%codes>; my @op_names := MAST::Ops.WHO<@names>; my %generators := MAST::Ops.WHO<%generators>; method new(:$frame!, str :$op!, *@operands) { %generators{$op}($frame, |@operands) } method new_with_operand_array(@operands, :$frame!, str :$op!) { %generators{$op}($frame, |@operands) } } # An extension operation to be executed. The operands must be either # registers, literals or labels (depending on what the instruction needs). class MAST::ExtOp is MAST::Node { method new(str :$op!, :$cu!, *@operands) { self.new_with_operand_array(@operands, :$op, :$cu) } method new_with_operand_array(@operands, str :$op!, :$cu!) { my $bytecode := $*MAST_FRAME.bytecode; my uint $op_code := $cu.get_extop_code($op); my @extop_sigs := nqp::getattr($*MAST_FRAME.compunit, MAST::CompUnit, '@!extop_sigs'); nqp::die("Invalid extension op $op specified") if $op_code < 1024 || $op_code - 1024 >= nqp::elems(@extop_sigs); # EXTOP_BASE my @operand_sigs := @extop_sigs[$op_code - 1024]; $bytecode.write_uint16($op_code); my $num_operands := nqp::elems(@operand_sigs); nqp::die("Instruction has invalid number of operads") if nqp::elems(@operands) != $num_operands; my int $idx := 0; while $idx < $num_operands { my $flags := nqp::atpos_i(@operand_sigs, $idx); my $rw := $flags +& $MVM_operand_rw_mask; my $type := $flags +& $MVM_operand_type_mask; $*MAST_FRAME.compile_operand($bytecode, $rw, $type, @operands[$idx]); $idx++; } } } # A series of instructions that fall on a particular line in a particular source file class MAST::Annotated is MAST::Node { method new(:$file = '', :$line!) { $*MAST_FRAME.add-annotation(:$file, :$line); } } # Handler constants. module HandlerAction { our $unwind_and_goto := 0; our $unwind_and_goto_with_payload := 1; our $invoke_and_we'll_see := 2; } # Category constants. module HandlerCategory { our $catch := 1; our $control := 2; our $next := 4; our $redo := 8; our $last := 16; our $return := 32; our $unwind := 64; our $take := 128; our $warn := 256; our $succeed := 512; our $proceed := 1024; our $labeled := 4096; our $await := 8192; our $emit := 16384; our $done := 32768; } # A region with a handler. class MAST::HandlerScope is MAST::Node { method new(:$start, :$category_mask!, :$action!, :$goto!, :$block, :$label) { unless nqp::istype($goto, MAST::Label) { nqp::die("Handler needs a MAST::Label to unwind to"); } if $action == $HandlerAction::invoke_and_we'll_see { unless nqp::istype($block, MAST::Local) { nqp::die("Handler action invoke-and-we'll-see needs a MAST::Local to invoke"); } } elsif $action != $HandlerAction::unwind_and_goto && $action != $HandlerAction::unwind_and_goto_with_payload { nqp::die("Unknown handler action"); } if $category_mask +& $HandlerCategory::labeled { unless nqp::istype($label, MAST::Local) { nqp::die("Handler category 'labeled' needs a MAST::Local"); } } $*MAST_FRAME.add-handler-scope(:$start, :$category_mask, :$action, :$goto, :$block, :$label); } } sub get_typename($type) { ["obj","int","num","str","","","","","","","uint"][nqp::objprimspec($type)] } class MoarVM::Handler { has int32 $!start_offset; has int32 $!end_offset; has int32 $!category_mask; has $!action; has $!label; has $!label_reg; has $!local; method BUILD(:$start_offset, :$end_offset, :$category_mask, :$action, :$label) { $!start_offset := $start_offset; $!end_offset := $end_offset; $!category_mask := $category_mask; $!action := $action; $!label := $label; } method start_offset() { $!start_offset } method end_offset() { $!end_offset } method category_mask() { $!category_mask } method action() { $!action } method label() { $!label } method label_reg() { $!label_reg } method set_label_reg($l) { $!label_reg := $l } method local() { $!local } method set_local($l) { $!local := $l } method add-offset(uint32 $offset, uint32 $after) { $!start_offset := $!start_offset + $offset if $!start_offset >= $after; $!end_offset := $!end_offset + $offset if $!end_offset >= $after; } } # Represents a frame, which is a unit of invocation. This captures the # static aspects of a frame. class MAST::Frame is MAST::Node { # A compilation-unit unique identifier for the frame. has str $!cuuid; # A name (need not be unique) for the frame. has str $!name; # The set of lexicals that we allocate space for and keep until # nothing references an "instance" of the frame. This is the # list of lexical types, the index being significant. Any type # that has a flattening representation will be "flattened" in to # the frame itself. has @!lexical_types; # Mapping of lexical names to slot indexes. has @!lexical_names; # The set of locals we allocate, but don't need once the frame # has finished executing. This is the set of types. Note that # they do not get a name. has @!local_types; # The outer frame, if any. has $!outer; # Mapping of lexical names to lexical index, for lookups. has %!lexical_map; # Mapping of names to local indexes, for debuging. has %!debug_map; # Integer array with alternating pairings of local index and debug name # string heap index. has @!debug_map_idxs; # Flag bits. my int $FRAME_FLAG_EXIT_HANDLER := 1; my int $FRAME_FLAG_IS_THUNK := 2; my int $FRAME_FLAG_HAS_CODE_OBJ := 4; my int $FRAME_FLAG_NO_INLINE := 8; my int $FRAME_FLAG_HAS_INDEX := 32768; # Can go after a rebootstrap. my int $FRAME_FLAG_HAS_SLV := 65536; # Can go after a rebootstrap. has int $!flags; # The frame index in the compilation unit (cached to aid assembly). has int $!frame_idx; # Integer array with 4 entries per static lexical value: # - The lexical index in the frame # - A flag (0 = static, 1 = container var, 2 = state var) # - SC index in this compilation unit # - Index of the object within that SC has @!static_lex_values; # Code object SC dependency index and SC index. has int $!code_obj_sc_dep_idx; has int $!code_obj_sc_idx; has $!writer; has $!compunit; has $!string-heap; has $!callsites; has uint32 $!cuuid-idx; has uint32 $!name-idx; has $!bytecode; has uint32 $!bytecode-offset; has %!labels; has @!labels; has %!label-fixups; has @!lexical_names_idxs; has $!annotations; has int32 $!annotations-offset; has $!num-annotations; has @!handlers; has @!buffer-stack; has @!child-label-fixups; method WHICH() { "MAST::Frame|$!cuuid|$!name" } method raku() { "MAST::Frame.new(:cuuid($!cuuid), :name<$!name>)" } class SubBuffer { has $!bytecode; has int32 $!annotations-offset; has int32 $!annotations-end; has %!labels; has @!label-fixups; has @!handlers; method new($bytecode, int32 $annotations-offset, %labels, @label-fixups, @handlers) { my $obj := nqp::create(self); $obj.BUILD($bytecode, $annotations-offset, %labels, @label-fixups, @handlers); $obj } method BUILD($bytecode, int32 $annotations-offset, %labels, @label-fixups, @handlers) { $!bytecode := $bytecode; $!annotations-offset := $annotations-offset; %!labels := %labels; @!label-fixups := @label-fixups; @!handlers := @handlers; } method bytecode() { $!bytecode } method annotations-offset() { $!annotations-offset } method annotations-end() { $!annotations-end } method label-fixups() { @!label-fixups } method labels() { %!labels } method handlers() { @!handlers } method end-annotations(int32 $offset) { $!annotations-end := $offset }; } method start_subbuffer() { nqp::push(@!buffer-stack, SubBuffer.new( $!bytecode := nqp::create(MAST::Bytecode), $!annotations-offset := nqp::elems($!annotations), %!labels := nqp::hash, @!child-label-fixups := nqp::list_i, @!handlers := nqp::list, )); nqp::setelems($!bytecode, $initial_bytecode_size); nqp::setelems($!bytecode, 0); } method end_subbuffer() { my $subbuffer := nqp::pop(@!buffer-stack); my $current := @!buffer-stack[nqp::elems(@!buffer-stack) - 1]; $!bytecode := $current.bytecode; @!child-label-fixups := $current.label-fixups; %!labels := $current.labels; $!annotations-offset := $current.annotations-offset; @!handlers := $current.handlers; $subbuffer.end-annotations(nqp::elems($!annotations)); $subbuffer } method insert_bytecode($subbuffer, uint32 $insert_offset) { my $subbytecode := $subbuffer.bytecode; my uint32 $offset := nqp::elems($subbytecode); # if there's a label at $insert_offset and there's already an instruction, we assume # that we need to move the label with the instruction. If there's just a label but # no instruction yet, we assume the label was meant for the inserted instruction. my int $include_pos := ($insert_offset < nqp::elems($!bytecode)); my $iter := nqp::iterator(@!child-label-fixups); while $iter { my uint32 $at := nqp::shift_i($iter); my uint32 $pos := $!bytecode.read_uint32_at($at); if $include_pos ?? $pos >= $insert_offset !! $pos > $insert_offset { $pos := $pos + $offset; $!bytecode.write_uint32_at($pos, $at); } } $iter := nqp::iterator($subbuffer.label-fixups); while $iter { my uint32 $at := nqp::shift_i($iter); my uint32 $pos := $subbytecode.read_uint32_at($at); $pos := $pos + $insert_offset; $subbytecode.write_uint32_at($pos, $at); nqp::push_i(@!child-label-fixups, $at + $insert_offset); # for nested subbuffers } for %!labels { my uint32 $pos := nqp::iterval($_); if $include_pos ?? $pos >= $insert_offset !! $pos > $insert_offset { $pos := $pos + $offset; %!labels{nqp::iterkey_s($_)} := $pos; } } for @!handlers { $_.add-offset($offset, $insert_offset); } my uint32 $at := $!annotations-offset; my uint32 $end := $subbuffer.annotations-offset; my uint32 $ann-size := 3 * 4; while $at < $end { my uint32 $pos := $!annotations.read_uint32_at($at); if $pos >= $insert_offset { $pos := $pos + $offset; $!annotations.write_uint32_at($pos, $at); } $at := $at + $ann-size; } if $insert_offset > 0 { for $subbuffer.labels { %!labels{nqp::iterkey_s($_)} := nqp::iterval($_) + $insert_offset; } my uint $zero := 0; for $subbuffer.handlers { $_.add-offset($insert_offset, $zero); nqp::push(@!handlers, $_); } $end := $subbuffer.annotations-end; while $at < $end { my uint32 $pos := $!annotations.read_uint32_at($at); $pos := $pos + $insert_offset; $!annotations.write_uint32_at($pos, $at); $at := $at + $ann-size; } } $!bytecode.write_buf_at($subbytecode, $insert_offset); } my int $cuuid_src := 0; sub fresh_id() { $cuuid_src++; "!MVM_CUUID_$cuuid_src" } method new(:$cuuid = fresh_id(), :$name = '', :$writer, :$compunit) { my $obj := nqp::create(self); $obj.BUILD($cuuid, $name, $writer, $compunit); $obj } method BUILD($cuuid, $name, $writer, $compunit) { $!cuuid := $cuuid; $!name := $name; @!lexical_types := nqp::list(); @!lexical_names := nqp::list(); @!local_types := nqp::list(); $!outer := MAST::Node; %!lexical_map := nqp::hash(); %!debug_map := nqp::hash(); @!static_lex_values := nqp::list_i(); $!writer := $writer; $!compunit := $compunit; $!string-heap := $writer.string-heap; $!callsites := $writer.callsites; $!annotations := MAST::Bytecode.new; $!annotations-offset := nqp::elems($!annotations); $!num-annotations := 0; $!bytecode := MAST::Bytecode.new; $!cuuid-idx := $!string-heap.add($!cuuid); $!name-idx := $!string-heap.add($!name); @!handlers := nqp::list; %!labels := nqp::hash; @!labels := nqp::list; %!label-fixups := nqp::hash; @!buffer-stack := nqp::list; @!child-label-fixups := nqp::list_i; nqp::setelems($!bytecode, $initial_bytecode_size); nqp::setelems($!bytecode, 0); nqp::setelems($!annotations, $initial_annotations_size); nqp::setelems($!annotations, 0); nqp::push(@!buffer-stack, SubBuffer.new($!bytecode, 0, %!labels, @!child-label-fixups, @!handlers)); } method prepare() { @!lexical_names_idxs := nqp::list_i; @!debug_map_idxs := nqp::list_i(); for @!lexical_names { nqp::push_i(@!lexical_names_idxs, self.add-string($_)); } for sorted_keys(%!debug_map) { nqp::push_i(@!debug_map_idxs, %!debug_map{$_}.index); nqp::push_i(@!debug_map_idxs, self.add-string($_)); } } method clear_index() { $!frame_idx := -1; $!flags := nqp::bitand_i($!flags, nqp::bitneg_i($FRAME_FLAG_HAS_INDEX)); } method set_index(int $idx) { $!frame_idx := $idx; $!flags := nqp::bitor_i($!flags, $FRAME_FLAG_HAS_INDEX); } method add_lexical($type, $name) { my int $index := nqp::elems(@!lexical_types); @!lexical_types[$index] := $type; @!lexical_names[$index] := $name; %!lexical_map{$name} := $index; $index } method lexical_index($name) { nqp::existskey(%!lexical_map, $name) ?? %!lexical_map{$name} !! nqp::die("No such lexical '$name'") } method add_static_lex_value($index, $flags, $sc_idx, $idx) { my @slv := @!static_lex_values; nqp::push_i(@slv, $index); nqp::push_i(@slv, $flags); nqp::push_i(@slv, $sc_idx); nqp::push_i(@slv, $idx); $!flags := nqp::bitor_i($!flags, $FRAME_FLAG_HAS_SLV); } method add_local($type) { my int $index := nqp::elems(@!local_types); @!local_types[$index] := $type; $index } method add_local_debug_name($name, $local) { %!debug_map{$name} := $local; } method debug_map() { %!debug_map } method debug_map_idxs() { @!debug_map_idxs } method set_outer($outer) { if nqp::istype($outer, MAST::Frame) { $!outer := $outer; } else { nqp::die("set_outer expects a MAST::Frame"); } } method cuuid() { $!cuuid } method name() { $!name } method outer() { $!outer } method writer() { $!writer } method has_exit_handler($value = -1) { if $value > 0 { $!flags := nqp::bitor_i($!flags, $FRAME_FLAG_EXIT_HANDLER); } nqp::bitand_i($!flags, $FRAME_FLAG_EXIT_HANDLER) } method is_thunk($value = -1) { if $value > 0 { $!flags := nqp::bitor_i($!flags, $FRAME_FLAG_IS_THUNK); } nqp::bitand_i($!flags, $FRAME_FLAG_IS_THUNK) } method no_inline($value = -1) { if $value > 0 { $!flags := nqp::bitor_i($!flags, $FRAME_FLAG_NO_INLINE); } nqp::bitand_i($!flags, $FRAME_FLAG_NO_INLINE) } method set_code_object_idxs(int $sc_dep_idx, int $sc_idx) { $!code_obj_sc_dep_idx := $sc_dep_idx; $!code_obj_sc_idx := $sc_idx; $!flags := nqp::bitor_i($!flags, $FRAME_FLAG_HAS_CODE_OBJ); } method dump_lines(@lines, $indent) { nqp::push(@lines, $indent~"MAST::Frame name<$!name>, cuuid<$!cuuid>"); if !nqp::chars($indent) { my $lex; my $x := 0; my $locals := "$indent Local types: "; $locals := $locals ~ $x++ ~ "<" ~ get_typename($_) ~ ">, " for @!local_types; nqp::push(@lines, $locals); if nqp::elems(@!lexical_types) { $x := 0; $lex := "$indent Lexical types: "; $lex := $lex ~ $x++ ~ "<" ~ get_typename($_) ~ ">, " for @!lexical_types; nqp::push(@lines, $lex); } if nqp::elems(@!lexical_names) { $x := 0; $lex := "$indent Lexical names: "; $lex := $lex ~ $x++ ~ "<$_>, " for @!lexical_names; nqp::push(@lines, $lex); } if nqp::elems(%!lexical_map) { $lex := "$indent Lexical map: "; $lex := "$lex$_" ~ '<' ~ %!lexical_map{$_} ~ '>, ' for %!lexical_map; nqp::push(@lines, $lex); } nqp::push(@lines, "$indent Outer: " ~ ( $!outer && $!outer.cuuid ne $!cuuid ?? "name<" ~ $!outer.name ~ ">, cuuid<"~$!outer.cuuid ~ '>' !! "" )); nqp::push(@lines, ''); } } method cuuid-idx() { $!cuuid-idx } method name-idx() { $!name-idx } method bytecode() { $!bytecode } method bytecode-length() { nqp::elems($!bytecode) } method bytecode-offset() { $!bytecode-offset } method annotations() { $!annotations } method annotations-offset() { $!annotations-offset } method set-annotations-offset($offset) { $!annotations-offset := $offset } method set-bytecode-offset($offset) { $!bytecode-offset := $offset } method local_types() { @!local_types } method lexical_types() { @!lexical_types } method lexical-name-idxs() { @!lexical_names_idxs } method static_lex_values() { @!static_lex_values } method flags() { $!flags } method code_obj_sc_dep_idx() { $!code_obj_sc_dep_idx } method code_obj_sc_idx() { $!code_obj_sc_idx } method num-annotations() { $!num-annotations } method handlers() { @!handlers } method size() { my uint32 $size := 54 + 2 * nqp::elems(self.local_types) + 6 * nqp::elems(self.lexical_types) + 12 * nqp::elems(self.static_lex_values) / 4 + 6 * nqp::elems(self.debug_map); for @!handlers { $size := $size + 20; if $_.category_mask +& 4096 { $size := $size + 2; } } $size } method add-string(str $s) { $!string-heap.add($s); } method callsites() { $!callsites } method compunit() { $!compunit } method labels() { %!labels } method label-fixups() { %!label-fixups } method resolve-label($label) { %!labels{$label.id} } method keep-label(MAST::Label $l) { nqp::push(@!labels, $l); } method add-label(MAST::Label $i) { my uint $pos := nqp::elems($!bytecode); my str $key := $i.id; if %!labels{$key} { nqp::die("Duplicate label at $pos"); } %!labels{$key} := $pos; if nqp::existskey(%!label-fixups, $key) { my $iter := nqp::iterator(%!label-fixups{$key}); while $iter { $!bytecode.write_uint32_at($pos, my uint32 $id := nqp::shift_i($iter)); } } } my @kind_to_args := [0, $Arg::int, # $MVM_reg_int8 := 1; $Arg::int, # $MVM_reg_int16 := 2; $Arg::int, # $MVM_reg_int32 := 3; $Arg::int, # $MVM_reg_int64 := 4; $Arg::num, # $MVM_reg_num32 := 5; $Arg::num, # $MVM_reg_num64 := 6; $Arg::str, # $MVM_reg_str := 7; $Arg::obj # $MVM_reg_obj := 8; ]; method compile_label($bytecode, $arg) { my $key := $arg.id; my %labels := self.labels; nqp::push_i(@!child-label-fixups, nqp::elems($!bytecode)); if nqp::existskey(%labels, $key) { $bytecode.write_uint32(%labels{$key}); } else { my %label-fixups := self.label-fixups; my @fixups := nqp::existskey(%label-fixups, $key) ?? %label-fixups{$key} !! (%label-fixups{$key} := nqp::list_i); nqp::push_i(@fixups, nqp::elems($bytecode)); $bytecode.write_uint32(my uint $zero := 0); } } method compile_operand($bytecode, int $rw, int $type, $arg) { if $rw == nqp::const::MVM_OPERAND_LITERAL { if $type == nqp::const::MVM_OPERAND_INT64 { my uint $value := nqp::unbox_i($arg); $bytecode.write_uint64($value); } elsif $type == nqp::const::MVM_OPERAND_INT16 { my uint $value := nqp::unbox_i($arg); if $value < -32768 || 32767 < $value { nqp::die("Value outside range of 16-bit MAST::IVal"); } $bytecode.write_uint16($value); } elsif $type == nqp::const::MVM_OPERAND_NUM64 { $bytecode.write_double($arg); } elsif $type == nqp::const::MVM_OPERAND_STR { $bytecode.write_uint32(self.add-string($arg)); } elsif $type == nqp::const::MVM_OPERAND_INS { self.compile_label($bytecode, $arg); } elsif $type == nqp::const::MVM_OPERAND_CODEREF { nqp::die("Expected MAST::Frame, but didn't get one") unless $arg.isa(MAST::Frame); my uint16 $index := self.writer.get_frame_index($arg); $bytecode.write_uint16($index); } else { nqp::die("literal operand type $type NYI"); } } elsif $rw == nqp::const::MVM_OPERAND_READ_REG || $rw == nqp::const::MVM_OPERAND_WRITE_REG { nqp::die("Expected MAST::Local, but didn't get one") unless $arg.isa(MAST::Local); my @local_types := self.local_types; my uint $index := nqp::unbox_u($arg); if $index > nqp::elems(@local_types) { nqp::die("MAST::Local index out of range"); } my $local_type := @local_types[$index]; if ($type != nqp::bitshiftl_i(type_to_local_type($local_type), 3) && $type != nqp::const::MVM_OPERAND_TYPE_VAR) { nqp::die("MAST::Local of wrong type specified"); } $bytecode.write_uint16($index); } elsif $rw == nqp::const::MVM_OPERAND_READ_LEX || $rw == nqp::const::MVM_OPERAND_WRITE_LEX { nqp::die("Expected MAST::Lexical, but didn't get one") unless $arg.isa(MAST::Lexical); $bytecode.write_uint16($arg.index); $bytecode.write_uint16(nqp::getattr($arg, MAST::Lexical, '$!frames_out')); } else { nqp::die("Unknown operand mode $rw cannot be compiled"); } } method add-annotation(:$file, :$line) { $!annotations.write_uint32(my uint $elems := nqp::elems($!bytecode)); $!annotations.write_uint32(self.add-string($file)); $!annotations.write_uint32($line); $!num-annotations++; } method add-handler-scope(:$category_mask, :$action, :$goto, :$block, :$label, :$start!) { unless nqp::defined($start) { nqp::die("MAST::HandlerScope needs a start"); } my $handler := MoarVM::Handler.new( :start_offset($start), :end_offset(nqp::elems($!bytecode)), :$category_mask, :$action, :label($goto), ); nqp::push(@!handlers, $handler); if $category_mask +& 4096 { # MVM_EX_CATEGORY_LABELED nqp::die('MAST::Local required for HandlerScope with loop label') unless $label.isa(MAST::Local); nqp::die('MAST::Local index out of range in HandlerScope') if nqp::unbox_u($label) >= nqp::elems(self.local_types); nqp::die('MAST::Local for HandlerScope must be an object') if type_to_local_type(self.local_types()[nqp::unbox_u($label)]) != $MVM_reg_obj; $handler.set_label_reg($label.index); } if $action == 2 { # HANDLER_INVOKE $handler.set_local(nqp::getattr($block, MAST::Local, '$!index')); } elsif $action == 0 || $action == 1 { # HANDLER_UNWIND_GOTO || HANDLER_UNWIND_GOTO_OBJ $handler.set_local(0); } else { nqp::die('Invalid action code for handler scope'); } } method write_operand($i, $idx, $o) { my $flags := nqp::atpos_i(@MAST::Ops::values, nqp::atpos_i(@MAST::Ops::offsets, $i.op) + $idx); my $rw := $flags +& $MVM_operand_rw_mask; my $type := $flags +& $MVM_operand_type_mask; self.compile_operand($!bytecode, $rw, $type, $o); } }