#!/usr/bin/env perl -W # # %CopyrightBegin% # # Copyright Ericsson AB 1998-2017. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # %CopyrightEnd% # use strict; use vars qw($BEAM_FORMAT_NUMBER); use constant COLD => 0; use constant WARM => 1; use constant HOT => 2; # Instructions for packing use constant PACK_JUMP => 1; use constant PACK_IN_INSTR_WORD => 2; # Packing commands use constant PACK_CMD_TIGHTEST => '1'; use constant PACK_CMD_TIGHT => '2'; use constant PACK_CMD_LOOSE => '3'; use constant PACK_CMD_WIDE => '4'; $BEAM_FORMAT_NUMBER = undef; my $target = \&emulator_output; my $outdir = "."; # Directory for output files. my $verbose = 0; my $hotness = 1; my $num_file_opcodes = 0; my $wordsize = 32; my $code_pointers_are_short = 0; # Whether code pointers (to C code) are short. my $code_model = 'unknown'; my %defs; # Defines (from command line). # This is shift counts and mask for the packer. my $WHOLE_WORD = ''; my @basic_pack_options = (0); my @extended_pack_options = @basic_pack_options; # There are two types of instructions: generic and specific. # The generic instructions are those generated by the Beam compiler. # Corresponding to each generic instruction, there is generally a # whole family of related specific instructions. Specific instructions # are those executed by the VM interpreter during run-time. # Maximum number of operands for a generic instruction. # In beam_load.c the MAX_OPARGS refers to the maximum # number of operands for generic instructions. my $max_gen_operands = 8; # Maximum number of operands for a specific instruction. # Must be even. The beam_load.c file must be updated, too. my $max_spec_operands = 6; # The maximum number of primitive genop_types. my $max_genop_types = 16; my %gen_opnum; my %num_specific; my %gen_to_spec; my %specific_op; # Information about each specific operator. Key is the print name (e.g. get_list_xxy). # Value is a hash. my %spec_op_info; my %gen_arity; my @gen_arity; my @gen_opname; my @op_to_name; my @obsolete; # Instructions and micro instructions implemented in C. my %c_code; # C code block, location, arguments. my %c_code_used; # Used or not. # Definitions for instructions combined from micro instructions. my %combined_instrs; my @generated_code; # Generated code. my %sort_order; my @unnumbered_generic; my %unnumbered; my %is_transformed; # # Pre-processor. # my @if_val; my @if_line; # # Code transformations. # my $te_max_vars = 0; # Max number of variables ever needed. my %gen_transform; my %match_engine_ops; # All opcodes for the match engine. my %gen_transform_offset; my @transformations; my @call_table; my %call_table; my @pred_table; my %pred_table; # Operand types for generic instructions. my $compiler_types = "uiaxyfhz"; my $loader_types = "nprvlqo"; my $genop_types = $compiler_types . $loader_types; # # Defines the argument types and their loaded size assuming no packing. # my %arg_size = ('r' => 0, # x(0) - x register zero 'x' => 1, # x(N), N > 0 - x register 'y' => 1, # y(N) - y register 'i' => 1, # tagged integer 'a' => 1, # tagged atom 'n' => 0, # NIL (implicit) 'c' => 1, # tagged constant (integer, atom, nil) 's' => 1, # tagged source; any of the above 'S' => 1, # tagged source register (x or y) 'd' => 1, # tagged destination register (r, x, y) 'f' => 1, # failure label 'j' => 1, # either 'f' or 'p' 'e' => 1, # pointer to export entry 'L' => 0, # label 't' => 1, # untagged integer (12 bits) -- can be packed 'I' => 1, # untagged integer (32 bits) -- can be packed 'W' => 1, # untagged integer/pointer (one word) 'b' => 1, # pointer to bif 'A' => 1, # arity value 'P' => 1, # byte offset into tuple or stack 'Q' => 1, # like 'P', but packable 'h' => 1, # character 'l' => 1, # float reg 'q' => 1, # literal term ); # # Generate bits. # my %type_bit; my @tag_type; sub define_type_bit { my($tag,$val) = @_; defined $type_bit{$tag} and sanity("the tag '$tag' has already been defined with the value ", $type_bit{$tag}); $type_bit{$tag} = $val; } { my($bit) = 1; my(%bit); foreach (split('', $genop_types)) { push(@tag_type, $_); define_type_bit($_, $bit); $bit{$_} = $bit; $bit *= 2; } # Composed types. define_type_bit('d', $type_bit{'x'} | $type_bit{'y'}); define_type_bit('c', $type_bit{'i'} | $type_bit{'a'} | $type_bit{'n'} | $type_bit{'q'}); define_type_bit('s', $type_bit{'d'} | $type_bit{'i'} | $type_bit{'a'} | $type_bit{'n'} | $type_bit{'q'}); define_type_bit('S', $type_bit{'d'}); define_type_bit('j', $type_bit{'f'} | $type_bit{'p'}); # Aliases (for matching purposes). define_type_bit('t', $type_bit{'u'}); define_type_bit('I', $type_bit{'u'}); define_type_bit('W', $type_bit{'u'}); define_type_bit('A', $type_bit{'u'}); define_type_bit('L', $type_bit{'u'}); define_type_bit('b', $type_bit{'u'}); define_type_bit('e', $type_bit{'u'}); define_type_bit('P', $type_bit{'u'}); define_type_bit('Q', $type_bit{'u'}); } # # Pre-define the 'fail' instruction. It is used internally # by the 'try_me_else_fail' instruction. # $match_engine_ops{'TOP_fail'} = 1; # # Sanity checks. # { if (@tag_type > $max_genop_types) { sanity("\$max_genop_types is $max_genop_types, ", "but there are ", scalar(@tag_type), " primitive tags defined\n"); } foreach my $tag (@tag_type) { sanity("tag '$tag': primitive tags must be named with lowercase letters") unless $tag =~ /^[a-z]$/; } foreach my $tag (keys %arg_size) { defined $type_bit{$tag} or sanity("the tag '$tag' has a size in %arg_size, " . "but has no defined bit pattern"); } } # # Parse command line options. # while (@ARGV && $ARGV[0] =~ /^-(.*)/) { $_ = $1; shift; ($target = \&emulator_output), next if /^emulator/; ($target = \&compiler_output), next if /^compiler/; ($outdir = shift), next if /^outdir/; ($wordsize = shift), next if /^wordsize/; ($code_model = shift), next if /^code-model/; ($verbose = 1), next if /^v/; ($defs{$1} = $2), next if /^D(\w+)=(\w+)/; die "$0: Bad option: -$_\n"; } if ($wordsize == 32) { $defs{'ARCH_32'} = 1; $defs{'ARCH_64'} = 0; } elsif ($wordsize == 64) { $defs{'ARCH_32'} = 0; $defs{'ARCH_64'} = 1; $code_pointers_are_short = $code_model eq 'small'; } # # Initialize pack options. # if ($wordsize == 64) { @basic_pack_options = (0,PACK_JUMP); @extended_pack_options = @basic_pack_options; if ($code_pointers_are_short) { foreach (@basic_pack_options) { push @extended_pack_options, $_ | PACK_IN_INSTR_WORD; } } } # # Add placeholders for built-in macros. # $c_code{'IS_PACKED'} = ['$Expr',"built-in macro",('Expr')]; $c_code{'ARG_POSITION'} = ['$Expr',"built-in macro",('Expr')]; foreach my $name (keys %c_code) { $c_code_used{$name} = 1; } # # Parse the input files. # my $in_c_code = ''; my $c_code_block; my $c_code_loc; my @c_args; while (<>) { my($op_num); if ($in_c_code) { if (/^\}/) { my $name = $in_c_code; my $block = $c_code_block; $in_c_code = ''; $block =~ s/^ //mg; chomp $block; $c_code{$name} = [$block,$c_code_loc,@c_args]; } else { $c_code_block .= $_; } next; } chomp; if (s/\\$//) { $_ .= <>; redo unless eof(ARGV); } next if /^\s*$/; next if /^\#/; next if m@^//@; # # Handle %if. # if (/^\%if (\w+)/) { my $name = $1; my $val = $defs{$name}; defined $val or error("'$name' is undefined"); push @if_val, $val; push @if_line, $.; next; } elsif (/^\%unless (\w+)/) { my $name = $1; my $val = $defs{$name}; defined $val or error("'$name' is undefined"); push @if_val, !$val; push @if_line, $.; next; } elsif (/^\%else$/) { unless (@if_line) { error("%else without a preceding %if/%unless"); } $if_line[$#if_line] = $.; $if_val[$#if_val] = !$if_val[$#if_val]; next; } elsif (/^\%endif$/) { unless (@if_line) { error("%endif without a preceding %if/%unless/%else"); } pop @if_val; pop @if_line; next; } if (@if_val and not $if_val[$#if_val]) { next; } # # Handle assignments. # if (/^([\w_][\w\d_]+)=(.*)/) { no strict 'refs'; my($name) = $1; $$name = $2; next; } # # Handle %hot, %warm, and %cold. # if (/^\%hot/) { $hotness = HOT; next; } elsif (/^\%warm/) { $hotness = WARM; next; } elsif (/^\%cold/) { $hotness = COLD; next; } # # Handle transformations. # if (/=>/) { parse_transformation($_); next; } # # Handle C code blocks. # if (/^(\w[\w.]*)\(([^\)]*)\)\s*{/) { my $name = $1; $in_c_code = $name; $c_code_block = ''; @c_args = parse_c_args($2); $c_code_loc = "$ARGV($.)"; if (defined $c_code{$name}) { my $where = $c_code{$name}->[1]; error("$name: already defined at $where"); } next; } # # Handle definition of instructions in terms of # micro instructions. # if (/^(\w+)\s*:=\s*([\w.]+)\s*;\s*$/) { $combined_instrs{$1} = ["$ARGV($.)",$2]; next; } # # Parse off the number of the operation. # $op_num = undef; if (s/^(\d+):\s*//) { $op_num = $1; $op_num != 0 or error("Opcode 0 invalid"); error("Opcode $op_num already defined") if defined $gen_opname[$op_num]; } # # Parse: Name/Arity (generic instruction) # if (m@^(-)?(\w+)/(\d)\s*$@) { my($obsolete) = $1; my($name) = $2; my($arity) = $3; $name =~ /^[a-z]/ or error("Opname must start with a lowercase letter"); defined $gen_arity{$name} and $gen_arity{$name} != $arity and error("Opname $name already defined with arity $gen_arity{$name}"); defined $unnumbered{$name,$arity} and error("Opname $name already defined with arity $gen_arity{$name}"); if (defined $op_num) { # Numbered generic operation $gen_opname[$op_num] = $name; $gen_arity[$op_num] = $arity; $gen_opnum{$name,$arity} = $op_num; $gen_arity{$name} = $arity; $gen_to_spec{"$name/$arity"} = undef; $num_specific{"$name/$arity"} = 0; $obsolete[$op_num] = defined $obsolete; } else { # Unnumbered generic operation. push(@unnumbered_generic, [$name, $arity]); $unnumbered{$name,$arity} = 1; } next; } # # Parse specific instructions (only present in emulator/loader): # Name Arg1 Arg2... # my($name,$arity) = parse_specific_op($_); if (defined $op_num) { error("specific instructions must not be numbered"); } elsif (!defined($gen_arity{$name}) && !defined($unnumbered{$name,$arity})) { # # Create an unumbered generic instruction too. # push(@unnumbered_generic, [$name, $arity]); $unnumbered{$name,$arity} = 1; } } continue { if (eof(ARGV)) { close(ARGV); if (@if_line) { error("Unterminated %if/%unless/%else at " . "line $if_line[$#if_line]\n"); } } } $num_file_opcodes = @gen_opname; # # Number all generic operations without numbers. # { my $ref; foreach $ref (@unnumbered_generic) { my($name, $arity) = @$ref; my $op_num = @gen_opname; push(@gen_opname, $name); push(@gen_arity, $arity); $gen_opnum{$name,$arity} = $op_num; $gen_arity{$name} = $arity; $gen_to_spec{"$name/$arity"} = undef; $num_specific{"$name/$arity"} = 0; } } # # Produce output for the chosen target. # &$target(); # # Ensure that all C code implementations have been used. # { my(@unused) = grep(!$c_code_used{$_}, keys %c_code); foreach my $unused (@unused) { my(undef,$where) = @{$c_code{$unused}}; warn "$where: $unused is unused\n"; } die "\n" if @unused; } # # Produce output needed by the emulator/loader. # sub emulator_output { my $i; my $name; my $key; # Loop variable. # # Generate code and meta information for all instructions. # foreach $key (keys %specific_op) { foreach (@{$specific_op{$key}}) { my($name, $hotness, @args) = @$_; my $print_name = print_name($name, @args); my($size, $code, $pack_spec) = cg_basic($name, @args); if (defined $code) { $code = "OpCase($print_name):\n$code"; push @generated_code, [$hotness,$code,($print_name)]; } # Note: Some of the information below will be modified # for combined instructions. my %info = ('size' => $size, 'pack_spec' => $pack_spec, 'adj' => 0, 'args' => \@args); $spec_op_info{$print_name} = \%info; } } # # Combine micro instruction into instruction blocks and generate # code for them. # combine_micro_instructions(); # # Information about opcodes (beam_opcodes.c). # $name = "$outdir/beam_opcodes.c"; open(STDOUT, ">$name") || die "Failed to open $name for writing: $!\n"; comment('C'); print "#ifdef HAVE_CONFIG_H\n"; print "# include \"config.h\"\n"; print "#endif\n\n"; print '#include "sys.h"', "\n"; print '#include "erl_vm.h"', "\n"; print '#include "export.h"', "\n"; print '#include "erl_process.h"', "\n"; print '#include "bif.h"', "\n"; print '#include "erl_atom_table.h"', "\n"; print '#include "beam_load.h"', "\n"; print "\n"; print "const char tag_to_letter[] = {\n "; for ($i = 0; $i < length($genop_types); $i++) { print "'$tag_type[$i]', "; } for (; $i < @tag_type; $i++) { print "'_', "; } print "\n};\n"; print "\n"; # # Generate code for specific ops. # my $spec_opnum = 0; print "const OpEntry opc[] = {\n"; foreach $key (sort keys %specific_op) { $gen_to_spec{$key} = $spec_opnum; $num_specific{$key} = @{$specific_op{$key}}; # # Pick up all instructions and manufacture sort keys; we must have # the most specific instructions appearing first (e.g. an 'x' operand # should be matched before 's' or 'd'). # my(%items) = (); foreach (@{$specific_op{$key}}) { my($name, $hot, @args) = @{$_}; my($sign) = join('', @args); $sign =~ s/[?]//g; # The primitive types should sort before other types. my $sort_key = $sign; eval "\$sort_key =~ tr/$genop_types/./"; $sort_key .= ":$sign"; my $print_name = print_name($name, @args); $items{$sort_key} = $print_name; } # # Now call the generator for the sorted result. # foreach my $sort_key (sort keys %items) { my $print_name = $items{$sort_key}; my $info = $spec_op_info{$print_name}; my(@args) = @{$info->{'args'}}; @args = map { s/[?]$//; $_ } @args; my $arity = @args; # # Calculate the bit mask which should be used to match this # instruction. # my(@bits) = (0) x ($max_spec_operands/2); my($i); my $involves_r = 0; for ($i = 0; $i < $max_spec_operands && defined $args[$i]; $i++) { my $t = $args[$i]; my $bits = $type_bit{$t}; if ($t eq 'r') { $bits |= $type_bit{'x'}; $involves_r |= 1 << $i; } my $shift = $max_genop_types * ($i % 2); $bits[int($i/2)] |= $bits << $shift; } printf "/* %3d */ ", $spec_opnum; my $init = "{"; my $sep = ""; foreach (@bits) { $init .= sprintf("%s0x%X", $sep, $_); $sep = ","; } $init .= "}"; my $adj = $info->{'adj'}; my $size = $info->{'size'}; my $pack_spec = $info->{'pack_spec'}; my $sign = join '', @args; init_item($print_name, $init, $involves_r, $size, $adj, $pack_spec, $sign); $op_to_name[$spec_opnum] = $print_name; $spec_opnum++; } } print "};\n\n"; print "const int num_instructions = $spec_opnum;\n\n"; # # Print the array for instruction counts. # print "#ifdef ERTS_OPCODE_COUNTER_SUPPORT\n"; print "Uint erts_instr_count[$spec_opnum];\n"; print "#endif\n"; print "\n"; # # Generate transformations. # tr_gen(@transformations); # # Print the generic instruction table. # print "const GenOpEntry gen_opc[] = {\n"; for ($i = 0; $i < @gen_opname; $i++) { if ($i == $num_file_opcodes) { print "\n/*\n * Internal generic instructions.\n */\n\n"; } my($name) = $gen_opname[$i]; my($arity) = $gen_arity[$i]; printf "/* %3d */ ", $i; if (!defined $name) { init_item("", 0, 0, 0, -1); } else { my($key) = "$name/$arity"; my($tr) = defined $gen_transform_offset{$key} ? $gen_transform_offset{$key} : -1; my($spec_op) = $gen_to_spec{$key}; my($num_specific) = $num_specific{$key}; defined $spec_op or $obsolete[$gen_opnum{$name,$arity}] or $is_transformed{$name,$arity} or error("instruction $key has no specific instruction"); $spec_op = -1 unless defined $spec_op; init_item($name, $arity, $spec_op, $num_specific, $tr); } } print "};\n"; # # Information about opcodes (beam_opcodes.h). # $name = "$outdir/beam_opcodes.h"; open(STDOUT, ">$name") || die "Failed to open $name for writing: $!\n"; comment('C'); print "#ifndef __OPCODES_H__\n"; print "#define __OPCODES_H__\n\n"; print "#define BEAM_FORMAT_NUMBER $BEAM_FORMAT_NUMBER\n"; print "#define MAX_GENERIC_OPCODE ", $num_file_opcodes-1, "\n"; print "#define NUM_GENERIC_OPS ", scalar(@gen_opname), "\n"; print "#define NUM_SPECIFIC_OPS ", scalar(@op_to_name), "\n"; print "#define SCRATCH_X_REG 1023\n"; print "\n"; if ($wordsize == 32) { print "#if defined(ARCH_64)\n"; print qq[ #error "32-bit architecture assumed, but ARCH_64 is defined"\n]; print "#endif\n"; print "#define BEAM_LOOSE_MASK 0xFFF\n"; print "#define BEAM_TIGHT_MASK 0xFFC\n"; print "#define BEAM_LOOSE_SHIFT 16\n"; print "#define BEAM_TIGHT_SHIFT 10\n"; } elsif ($wordsize == 64) { print "#if !defined(ARCH_64)\n"; print qq[ #error "64-bit architecture assumed, but ARCH_64 not defined"\n]; print "#endif\n"; if ($code_pointers_are_short) { print "#if !defined(CODE_MODEL_SMALL)\n"; print qq[ #error "small code model assumed, but CODE_MODEL_SMALL not defined"\n]; print "#endif\n"; } print "#define BEAM_WIDE_MASK 0xFFFFFFFFull\n"; print "#define BEAM_LOOSE_MASK 0xFFFFull\n"; print "#define BEAM_TIGHT_MASK 0xFFFFull\n"; print "#define BEAM_TIGHTEST_MASK 0x3FFull\n"; print "#define BEAM_WIDE_SHIFT 32\n"; print "#define BEAM_LOOSE_SHIFT 16\n"; print "#define BEAM_TIGHT_SHIFT 16\n"; print "#define BEAM_TIGHTEST_SHIFT 10\n"; } print "\n"; # # Definitions of tags. # my $letter; my $tag_num = 0; comment('C', "The following operand types for generic instructions", "occur in beam files."); foreach $letter (split('', $compiler_types)) { print "#define TAG_$letter $tag_num\n"; $tag_num++; } print "\n"; comment('C', "The following operand types are only used in the loader."); foreach $letter (split('', $loader_types)) { print "#define TAG_$letter $tag_num\n"; $tag_num++; } print "\n#define BEAM_NUM_TAGS $tag_num\n\n"; $i = 0; foreach (sort keys %match_engine_ops) { print "#define $_ $i\n"; $i++; } print "#define NUM_TOPS $i\n"; print "\n"; print "#define TE_MAX_VARS $te_max_vars\n"; print "\n"; print "extern const char tag_to_letter[];\n"; print "extern const Uint op_transform[];\n"; print "\n"; for ($i = 0; $i < @op_to_name; $i++) { print "#define op_$op_to_name[$i] $i\n"; } print "\n"; print "#define NUMBER_OF_OPCODES ",scalar(@op_to_name),"\n"; for ($i = 0; $i < @op_to_name; $i++) { print "#define op_count_$op_to_name[$i] ",$i+scalar(@op_to_name),"\n"; } print "\n"; print "#define DEFINE_OPCODES"; foreach (@op_to_name) { print " \\\n&&lb_$_,"; } print "\n\n"; print "#define DEFINE_COUNTING_OPCODES"; foreach (@op_to_name) { print " \\\n&&lb_count_$_,"; } print "\n\n"; print "#define DEFINE_COUNTING_LABELS"; for ($i = 0; $i < @op_to_name; $i++) { my($name) = $op_to_name[$i]; print " \\\nCountCase($name): erts_instr_count[$i]++; goto lb_$name;"; } print "\n\n"; for ($i = 0; $i < @gen_opname; $i++) { print "#define genop_$gen_opname[$i]_$gen_arity[$i] $i\n" if defined $gen_opname[$i]; } print "#endif\n"; # # Extension of transform engine. # $name = "$outdir/beam_tr_funcs.h"; open(STDOUT, ">$name") || die "Failed to open $name for writing: $!\n"; comment('C'); tr_gen_call(@call_table); $name = "$outdir/beam_pred_funcs.h"; open(STDOUT, ">$name") || die "Failed to open $name for writing: $!\n"; comment('C'); tr_gen_call(@pred_table); # # Implementation of operations for emulator. # $name = "$outdir/beam_hot.h"; open(STDOUT, ">$name") || die "Failed to open $name for writing: $!\n"; comment('C'); print_code(HOT); $name = "$outdir/beam_warm.h"; open(STDOUT, ">$name") || die "Failed to open $name for writing: $!\n"; comment('C'); print_code(WARM); $name = "$outdir/beam_cold.h"; open(STDOUT, ">$name") || die "Failed to open $name for writing: $!\n"; comment('C'); print_code(COLD); } sub print_name { my($name,@args) = @_; my $sign = join '', @args; $sign =~ s/[?]//g; $sign ne '' ? "${name}_$sign" : $name; } sub init_item { my($sep) = ""; print "{"; foreach (@_) { if (!defined $_) { print "${sep}NULL"; } elsif (/^\{/) { print "$sep$_"; } elsif (/^-?\d+$/) { print "$sep$_"; } else { print "$sep\"$_\""; } $sep = ", "; } print "},\n"; } sub q { my($str) = @_; "\"$str\""; } sub print_code { my($include_hot) = @_; my %sorted; foreach my $ref (@generated_code) { my($hot,$code,@labels) = @$ref; next unless $hot == $include_hot; my($sort_key) = @labels; # Use the first label as sort key. $sorted{$sort_key} = $code; } foreach (sort keys %sorted) { print_indented_code($sorted{$_}); } } sub print_indented_code { my(@code) = @_; foreach my $chunk (@code) { my $indent = 0; foreach (split "\n", $chunk) { s/^\s*//; if (/\}/) { $indent -= 2; } if ($_ eq '') { print "\n"; } elsif (/^#/) { print $_, "\n"; } else { print ' ' x $indent, $_, "\n"; } if (/\{/) { $indent += 2; } } print "\n"; } } # # Produce output needed by the compiler back-end (assembler). # sub compiler_output { my($module) = 'beam_opcodes'; my($name) = "${module}.erl"; my($i); open(STDOUT, ">$outdir/$name") || die "Failed to open $name for writing: $!\n"; print "-module($module).\n"; comment('erlang'); print "-export([format_number/0]).\n"; print "-export([opcode/2,opname/1]).\n"; print "\n"; print "-spec format_number() -> $BEAM_FORMAT_NUMBER.\n"; print "format_number() -> $BEAM_FORMAT_NUMBER.\n\n"; print "-spec opcode(atom(), 0..", $max_gen_operands, ") -> 1..", $num_file_opcodes-1, ".\n"; for ($i = 0; $i < @gen_opname; $i++) { next unless defined $gen_opname[$i]; print "%%" if $obsolete[$i]; print "opcode(", quote($gen_opname[$i]), ", $gen_arity[$i]) -> $i;\n"; } print "opcode(Name, Arity) -> erlang:error(badarg, [Name,Arity]).\n\n"; print "-spec opname(1..", $num_file_opcodes-1, ") -> {atom(),0..", $max_gen_operands, "}.\n"; for ($i = 0; $i < @gen_opname; $i++) { next unless defined $gen_opname[$i]; print "opname($i) -> {", quote($gen_opname[$i]), ",$gen_arity[$i]};\n"; } print "opname(Number) -> erlang:error(badarg, [Number]).\n"; # # Generate .hrl file. # my($hrl_name) = "$outdir/${module}.hrl"; open(STDOUT, ">$hrl_name") || die "Failed to open $hrl_name for writing: $!\n"; comment('erlang'); for ($i = 0; $i < @tag_type && $i < 8; $i++) { print "-define(tag_$tag_type[$i], $i).\n"; } print "\n"; } # # Parse and store a specific operation. # sub parse_specific_op { my($name, @args) = split " ", shift; my $arity = @args; # Check for various errors. error("Bad opcode name '$name'") unless $name =~ /^[a-z][\w\d_]*$/; error("too many operands") if @args > $max_spec_operands; for (my $i = 0; $i < $arity; $i++) { my $arg = $args[$i]; $arg =~ s/[?]$//; foreach my $type (split(//, $arg)) { error("Argument " . ($i+1) . ": invalid type '$type'") unless defined $arg_size{$type}; } } if (defined $gen_opnum{$name,$arity} and $obsolete[$gen_opnum{$name,$arity}]) { error("specific instructions may not be specified for obsolete instructions"); } # Expand operands with multiple types to multiple instructions. # (For example, "get_list xy xy xy" will be expanded to six instructions.) my @res = ([]); foreach my $arg (@args) { my @old_res = @res; @res = (); my $marker = ($arg =~ s/[?]$//) ? '?' : ''; foreach my $type (split(//, $arg)) { foreach my $args_ref (@old_res) { my @args = @$args_ref; push @args, "$type$marker"; push @res, \@args; } } } # Store each specific instruction. my $key = "$name/$arity"; foreach my $args_ref (@res) { @args = @$args_ref; push @{$specific_op{$key}}, [$name,$hotness,@args]; } # Done. ($name,$arity); } sub parse_c_args { local($_) = @_; my @res; while (s/^(\w[\w\d]*)\s*//) { push @res, $1; s/^,\s*// or last; } $_ eq '' or error("garbage in argument list: $_"); @res; } sub error { my(@message) = @_; my($where) = $. ? "$ARGV($.): " : ""; die $where, @message, "\n"; } sub sanity { die "internal error: ", @_, "\n"; } sub comment { my($lang, @comments) = @_; my($prefix); if ($lang eq 'C') { print "/*\n"; $prefix = " * "; } elsif ($lang eq 'erlang') { $prefix = '%% '; } else { $prefix = '# '; } my(@prog) = split('/', $0); my($prog) = $prog[$#prog]; if (@comments) { my $line; foreach $line (@comments) { print "$prefix$line\n"; } } else { print "$prefix Warning: Do not edit this file.\n"; print "$prefix Auto-generated by '$prog'.\n"; } if ($lang eq 'C') { print " */\n"; } print "\n"; } # # Combine micro instruction into instruction blocks. # sub combine_micro_instructions { my %groups; # Sanity check, normalize micro instructions. foreach my $instr (keys %combined_instrs) { my $ref = $combined_instrs{$instr}; my($def_loc,$def) = @$ref; my($group,@subs) = split /[.]/, $def; my $arity = 0; @subs = map { "$group.$_" } @subs; foreach my $s (@subs) { my $code = $c_code{$s}; defined $code or error("$def_loc: no definition of $s"); $c_code_used{$s} = 1; my(undef,undef,@c_args) = @{$code}; $arity += scalar(@c_args); } push @{$groups{$group}}, [$instr,$arity,@subs]; } # Now generate code for each group. foreach my $group (sort keys %groups) { my($hotness,$code,@labels) = combine_instruction_group($group, @{$groups{$group}}); push @generated_code, [$hotness,$code,@labels]; } } sub combine_instruction_group { my($group,@in_instrs) = @_; my $gcode = ''; # Code for the entire group. my $group_hotness = COLD; # Get code for the head of the group (if any). my $head_name = "$group.head"; $c_code_used{$head_name} = 1; my $head_code_ref = $c_code{$head_name}; if (defined $head_code_ref) { my($head_code,$where,@c_args) = @{$head_code_ref}; @c_args and error("$where: no arguments allowed for " . "head function '$head_name()'"); $gcode = $head_code . "\n"; } # Variables. my %offsets; my @instrs; my %num_references; my $group_size = 999; # Do basic error checking. Associate operands of instructions # with the correct micro instructions. Calculate offsets for micro # instructions. foreach my $ref_instr (@in_instrs) { my($specific,$arity,@subs) = @$ref_instr; my $specific_key = "$specific/$arity"; my $specific_op_ref = $specific_op{$specific_key}; error("no $specific_key instruction") unless defined $specific_op_ref; foreach my $specific_op (@$specific_op_ref) { my($name, $hotness, @args) = @{$specific_op}; $group_hotness = $hotness unless $group_hotness >= $hotness; my $offset = 0; my @rest = @args; my @new_subs; my $print_name = print_name($specific, @args); my $opcase = $print_name; my $last = $subs[$#subs]; foreach my $s (@subs) { my $code = $c_code{$s}; my(undef,undef,@c_args) = @{$code}; my @first; foreach (0..$#c_args) { push @first, shift @rest; } my $size = cg_combined_size($s, @first); $offsets{$s} = $offset unless defined $offsets{$s} and $offsets{$s} < $offset; $offset += $size - 1; my $label = micro_label($s); $num_references{$label} = 0; push @new_subs, [$opcase,$label,$s,$size-1,@first]; $opcase = ''; } $spec_op_info{$print_name}->{'size'} = $offset + 1; $group_size = $offset if $group_size >= $offset; push @instrs, [$specific_key,@new_subs]; } } # Link the sub instructions for each instructions to each # other. my @all_instrs; foreach my $instr (@instrs) { my($specific_key,@subs) = @{$instr}; for (my $i = 0; $i < @subs; $i++) { my($opcase,$label,$s,$size,@args) = @{$subs[$i]}; my $next = ''; (undef,$next) = @{$subs[$i+1]} if $i < $#subs; $num_references{$next}++ if $next; my $instr_info = "$opcase:$label:$next:$s:$size:@args"; push @all_instrs, [$label,$offsets{$s},$instr_info]; } } my %order_to_instrs; my %label_to_offset; my %order_to_offset; foreach my $instr (@all_instrs) { my($label,$offset,$instr_info) = @$instr; my $sort_key = sprintf("%02d.%02d", $offset, $num_references{$label}); push @{$order_to_instrs{$sort_key}}, $instr_info; $label_to_offset{$label} = $offset; $order_to_offset{$sort_key} = $offset; } my(@slots) = sort {$a <=> $b} keys %order_to_instrs; # Now generate the code for the entire group. my $offset = 0; my @opcase_labels; my %down; my %up; for(my $i = 0; $i < @slots; $i++) { my $key = $slots[$i]; # Sort micro-instructions with OpCase before other micro-instructions. my(@instrs) = @{$order_to_instrs{$key}}; my $order_func = sub { my $a_key = ($a =~ /^:/) ? "1$a" : "0$a"; my $b_key = ($b =~ /^:/) ? "1$b" : "0$b"; $a_key cmp $b_key; }; @instrs = sort $order_func @instrs; my %seen; foreach my $instr (@instrs) { my($opcase,$label,$next,$s,$size,$args) = split ":", $instr; my(@first) = split " ", $args; my $seen_key = "$label:$next:" . scalar(@first); next if $opcase eq '' and $seen{$seen_key}; $seen{$seen_key} = 1; $seen_key .= $opcase; if ($opcase ne '') { $gcode .= "OpCase($opcase):\n"; push @opcase_labels, $opcase; } if ($num_references{$label}) { $gcode .= "$label:\n"; } my $flags = ''; my $transfer_to_next = ''; my $inc = 0; unless ($i == $#slots) { $flags = "-no_next"; my $next_offset = $label_to_offset{$next}; $inc = ($offset + $size) - $next_offset; $transfer_to_next = "I += $inc;\n" if $inc; $transfer_to_next .= "goto $next;\n\n"; } my($gen_code,$down,$up) = cg_combined_code($s, $flags, $offset, $group_size-$offset, $inc, @first); my $spec_label = "$opcase$label"; $down{$spec_label} = $down; $up{$spec_label} = $up; $gcode .= $gen_code . $transfer_to_next; } $offset = $order_to_offset{$slots[$i+1]} if $i < $#slots; } foreach my $print_name (@opcase_labels) { my $info = $spec_op_info{$print_name}; $info->{'adj'} = $info->{'size'} - $group_size - 1; } # # Assemble pack specifications for all instructions in the group. # foreach my $instr (@instrs) { my(undef,@subs) = @{$instr}; my $down = ''; my $up = ''; for (my $i = 0; $i < @subs; $i++) { my($opcase,$label) = @{$subs[$i]}; my $spec_label = "$opcase$label"; if (defined $down{$spec_label}) { $down = $down{$spec_label} . $down; $up = $up . $up{$spec_label}; } } my $print_name = $subs[0]->[0]; my $info = $spec_op_info{$print_name}; $info->{'pack_spec'} = build_pack_spec("$down:$up"); } ($group_hotness,"{\n$gcode\n}\n\n",@opcase_labels); } sub micro_label { my $label = shift; $label =~ s/[.]/__/g; $label; } # # Basic code generation for one instruction. # sub cg_basic { my($name,@args) = @_; my($size,$code,$pack_spec) = code_gen($name, \@extended_pack_options, '', 0, undef, undef, @args); $pack_spec = build_pack_spec($pack_spec); ($size,$code,$pack_spec); } # # Calculate size for a micro instruction. # sub cg_combined_size { my($name,@args) = @_; my($size) = code_gen($name, \@basic_pack_options, '', 0, undef, undef, @args); $size; } # # Generate code for a micro instruction. # sub cg_combined_code { my($name,$extra,$offset,$comp_size,$inc,@args) = @_; my($size,$code,$pack_spec) = code_gen($name, \@basic_pack_options, $extra, $offset, $comp_size, $inc, @args); if ($pack_spec eq '') { ($code,'',''); } else { my($down,$up) = split /:/, $pack_spec; ($code,$down,$up); } } sub code_gen { my($name,$pack_options,$extra_comments,$offset,$comp_size,$inc,@args) = @_; my $group_size = defined $comp_size ? $comp_size + $inc : undef; my $size = 0; my $flags = ''; my @f; my $prefix = ''; my $tmp_arg_num = 1; my $pack_spec = ''; my $var_decls = ''; # # Pack arguments for hot code with an implementation. # my $c_code_ref = $c_code{$name}; if (defined $c_code_ref and $name ne 'catch') { ($var_decls, $pack_spec, @args) = do_pack($name, $offset, $pack_options, @args); } # # Calculate the size of the instruction and generate each argument for # the macro. # my $need_block = 0; my $arg_offset = $offset; @args = map { s/[?]$//g; $_ } @args; foreach (@args) { my($this_size) = $arg_size{$_}; SWITCH: { /^packed:d:(\d):(.*)/ and do { $var_decls .= "Eterm dst = $2;\n" . "Eterm* dst_ptr = REG_TARGET_PTR(dst);\n"; push(@f, "*dst_ptr"); $this_size = $1; last SWITCH; }; /^packed:[a-zA-z]:(\d):(.*)/ and do { push(@f, $2); $this_size = $1; last SWITCH; }; /r/ and do { push(@f, "r(0)"); last SWITCH; }; /[lxyS]/ and do { push(@f, $_ . "b(" . arg_offset($arg_offset) . ")"); last SWITCH; }; /n/ and do { push(@f, "NIL"); last SWITCH; }; /s/ and do { my($tmp) = "targ$tmp_arg_num"; $var_decls .= "Eterm $tmp;\n"; $tmp_arg_num++; push(@f, $tmp); $prefix .= "GetR($arg_offset, $tmp);\n"; $need_block = 1; last SWITCH; }; /d/ and do { $var_decls .= "Eterm dst = " . arg_offset($arg_offset) . ";\n" . "Eterm* dst_ptr = REG_TARGET_PTR(dst);\n"; push(@f, "*dst_ptr"); last SWITCH; }; defined $arg_size{$_} and do { push @f, arg_offset($arg_offset); last SWITCH; }; die "$name: The generator can't handle $_, at"; } $size += $this_size; $arg_offset += $this_size; } # # If the implementation is in beam_emu.c, there is nothing # more to do. # unless (defined $c_code_ref) { return ($size+1, undef, ''); } $group_size = $size unless defined $group_size; # # Generate main body of the implementation. # my($c_code,$where,@c_args) = @{$c_code_ref}; my %bindings; $c_code_used{$name} = 1; if (@f != @c_args) { error("$where: defining '$name' with ", scalar(@c_args), " arguments instead of expected ", scalar(@f), " arguments"); } for (my $i = 0; $i < @f; $i++) { my $var = $c_args[$i]; $bindings{$var} = $f[$i]; } $bindings{'NEXT_INSTRUCTION'} = "I+" . ($group_size+$offset+1); $bindings{'IP_ADJUSTMENT'} = defined $inc ? $inc : 0; $c_code = eval { expand_all($c_code, \%bindings) }; unless (defined $c_code) { warn $@; error("... from the body of $name at $where"); } my(@comments) = $c_code =~ m@//[|]\s*(.*)@g; $c_code =~ s@//[|]\s*(.*)\n?@@g; $flags = "@comments $extra_comments"; # # Generate code for transferring to the next instruction. # my $dispatch_next; my $instr_offset = $group_size + $offset + 1; if ($flags =~ /-no_next/) { $dispatch_next = ""; } elsif ($flags =~ /-no_prefetch/) { $dispatch_next = "\nI += $instr_offset;\n" . "ASSERT(VALID_INSTR(*I));\n" . "Goto(*I);"; } else { $var_decls .= "BeamInstr next_pf = BeamCodeAddr(I[$instr_offset]);\n"; $dispatch_next = "\nI += $instr_offset;\n" . "ASSERT(VALID_INSTR(next_pf));\n" . "GotoPF(next_pf);"; } # # Assemble the complete code for the instruction. # my $body = "$c_code$dispatch_next"; if ($need_block) { $body = "$prefix\{\n$body\n}"; } else { $body = "$prefix$body"; } my $code = join("\n", "{", "$var_decls$body", "}", ""); ($size+1, $code, $pack_spec); } sub arg_offset { my $offset = shift; "I[" . ($offset+1) . "]"; } sub expand_all { my($code,$bindings_ref) = @_; my %bindings = %{$bindings_ref}; # Expand all $Var occurrences. $code =~ s/[\$](\w[\w\d]*)(?!\()/defined $bindings{$1} ? $bindings{$1} : "\$$1"/ge; # Find calls to macros, $name(...), and expand them. my $res = ""; while ($code =~ /[\$](\w[\w\d]*)\(/) { my $macro_name = $1; my $keep = substr($code, 0, $-[0]); my $after = substr($code, $+[0]); my $body; ($body,$code) = expand_macro($macro_name, $after, \%bindings); $res .= "$keep$body"; } $res . $code; } sub expand_macro { my($name,$rest,$bindings_ref) = @_; my $c_code = $c_code{$name}; defined $c_code or error("calling undefined macro '$name'..."); $c_code_used{$name} = 1; my ($body,$where,@vars) = @{$c_code}; # Separate the arguments into @args; my @args; my $level = 1; my %inc = ('(' => 1, ')' => -1, '[' => 1, ']' => -1, '{' => 1, '}' => -1); my $arg = undef; while ($rest =~ /([,\(\[\{\}\]\)]|([^,\(\[\{\}\]\)]*))/g) { my $token = $1; my $inc = $inc{$token} || 0; $level += $inc; if ($level == 0) { $rest = substr($rest, pos($rest)); push @args, $arg if defined $arg; last; } if ($token eq ',') { if ($level == 1) { push @args, $arg; $arg = ""; } next; } $arg .= $token; } # Trim leading whitespace from each argument. foreach my $arg (@args) { $arg =~ s/^\s*//; } # Make sure that the number of arguments are correct. if (@vars != @args) { error("calling $name with ", scalar(@args), " arguments instead of expected ", scalar(@vars), " arguments..."); } # Now combine bindings from the parameter names and arguments. my %bindings = %{$bindings_ref}; my %new_bindings; # Keep the special, pre-defined bindings. foreach my $key (qw(NEXT_INSTRUCTION IP_ADJUSTMENT)) { $new_bindings{$key} = $bindings{$key}; } for (my $i = 0; $i < @vars; $i++) { my $arg = $args[$i]; $arg = eval { expand_all($arg, \%bindings) }; unless (defined $arg) { warn $@; die "... from the body of $name at $where\n"; } $new_bindings{$vars[$i]} = $arg; } $body = eval { expand_all($body, \%new_bindings) }; unless (defined $body) { warn $@; die "... from the body of $name at $where\n"; } # Handle built-in macros. if ($name eq 'ARG_POSITION') { if ($body =~ /^I\[(\d+)\]$/) { $body = $1; } else { $body = 0; } } elsif ($name eq 'IS_PACKED') { $body = ($body =~ /^I\[\d+\]$/) ? 0 : 1; } # Wrap body if needed and return resul.t $body = "do {\n$body\n} while (0)" if needs_do_wrapper($body); ($body,$rest); } # Conservative heuristic to determine whether a do { ... } while(0) # wrapper is needed. sub needs_do_wrapper { local $_ = shift; s@^//[|][^\n]*\n@@; s@^\s*@@s; s@^/[*].*[*]/\s*@@s; return 1 if /^(Eterm|Uint|Sint|int|unsigned)/; # Definitely needed. return 0 if /^do/; return 0 if /^SET_I/; return 0 if /^SET_CP/; return 0 if /^ERTS_NO_FPE_CHECK_INIT/; return 0 if /^ASSERT/; return 0 if /^DTRACE/; return 0 if /^[A-Za-z_]*\s*=/; return 0 if /^c_p->/; return 0 if /^[A-Z_]*SWAPOUT/; return 0 if /^if\s*[(]/; return 0 if /^goto\b/; return 0 if /^\d+/; return 1; # Not sure, say that it is needed. } sub do_pack { my($name,$offset,$pack_options,@args) = @_; @args = map { s/[?]$//; $_ } @args; my $ret = ['', ':', @args]; my $score = 0; foreach my $options (@$pack_options) { my($this_score,$this_result) = do_pack_one($name, $options, $offset, @args); if ($this_score > $score) { $ret = $this_result; $score = $this_score; } } return @$ret; } sub do_pack_one { my($name,$options,$offset,@args) = @_; my($packable_args) = 0; my @bits_needed; # Bits needed for each argument. my $pack_in_iw = $options & PACK_IN_INSTR_WORD; # # Define the minimum number of bits needed for the packable argument types. # my %bits_needed = ('x' => 10, 'y' => 10, 'Q' => 10, 'l' => 10, 'S' => 16, 'd' => 16, 't' => 16); if ($wordsize == 64) { $bits_needed{'I'} = 32; if ($options & PACK_JUMP) { $bits_needed{'f'} = 32; $bits_needed{'j'} = 32; } } # # Count the number of packable arguments. # foreach my $arg (@args) { if (defined $bits_needed{$arg}) { $packable_args++; push @bits_needed, $bits_needed{$arg}; } else { push @bits_needed, 0; } if ($arg =~ /^[fj]$/) { # Only pack the first occurrence of 'f' or 'j'. delete $bits_needed{'f'}; delete $bits_needed{'j'}; } } # # Return if there is nothing to pack. # if ($packable_args == 0) { return (-1); } elsif ($packable_args == 1 and !$pack_in_iw) { return (-1); } # # Determine how many arguments we should pack into each word. # my @args_per_word; my @need_wide_mask; my $bits; my $this_wordsize; my $word = -1; my $next_word = sub { $word++; $args_per_word[$word] = 0; $need_wide_mask[$word] = 0; $bits = 0; $this_wordsize = $wordsize; }; $next_word->(); $this_wordsize = 32 if $pack_in_iw; for (my $i = 0; $i < @args; $i++) { my $needed = $bits_needed[$i]; next unless $needed; if ($bits+$needed > $this_wordsize) { # Does not fit. $next_word->(); } if ($args_per_word[$word] == 4) { # Can't handle more than 4 args. $next_word->(); } if ($needed == 32 and $args_per_word[$word] > 1) { # Must only pack two arguments in this word, and there # are already at least two arguments here. $next_word->(); } $args_per_word[$word]++; $bits += $needed; if ($needed == 32) { $need_wide_mask[$word]++; } if ($need_wide_mask[$word] and $bits > 32) { # Can only pack two things in a word where one # item is 32 bits. Force the next item into # the next word. $bits = $this_wordsize; } } # # Try to balance packing between words. # if (@args_per_word == 1 and $args_per_word[0] == 1 and $pack_in_iw) { # Don't rebalance. } elsif ($args_per_word[$#args_per_word] == 1) { if ($args_per_word[$#args_per_word-1] < 3) { pop @args_per_word; } else { $args_per_word[$#args_per_word-1]--; $args_per_word[$#args_per_word]++; } } elsif (@args_per_word == 2 and $args_per_word[0] == 4 and $args_per_word[1] == 2) { $args_per_word[0] = 3; $args_per_word[1] = 3; } elsif (@args_per_word == 2 and $args_per_word[0] == 3 and $args_per_word[1] == 1) { $args_per_word[0] = 2; $args_per_word[1] = 2; } my $size = 0; my $pack_prefix = ''; my $down = ''; # Pack commands (towards instruction # beginning). my $up = ''; # Pack commands (storing back while # moving forward). # Skip an unpackable argument. my $skip_unpackable = sub { my($arg) = @_; if ($arg_size{$arg}) { # Save the argument on the pack engine's stack. my $push = 'g'; if ($type_bit{$arg} & $type_bit{'q'}) { # The operand may be a literal. $push = 'q'; } elsif ($type_bit{$arg} & $type_bit{'f'}) { # The operand may be a failure label. $push = 'f'; } $down = "$push${down}"; $up = "${up}p"; } }; # # Now generate the packing instructions. One complication is that # the packing engine works from right-to-left, but we must generate # the instructions from left-to-right because we must calculate # instruction sizes from left-to-right. my $arg_num = 0; for (my $word = 0; $word < @args_per_word; $word++) { my $ap = 0; # Argument number within word. my $packed_var = "tmp_packed" . ($word+1); my $args_per_word = $args_per_word[$word]; my $pack_word_size = ($pack_in_iw && $word == 0) ? 32 : $wordsize; my($shref,$mref,$iref,$unpack_suffix) = get_pack_parameters($name, $args_per_word, $pack_word_size, $need_wide_mask[$word]); my @shift = @$shref; my @mask = @$mref; my @instr = @$iref; while ($ap < $args_per_word) { my $reg = $args[$arg_num]; my $this_size = $arg_size{$reg}; if ($bits_needed[$arg_num]) { $this_size = 0; if ($ap == 0) { my $packed_data; if ($pack_in_iw and $word == 0) { $packed_data = "BeamExtraData(I[0])"; if ($args_per_word == 1) { $packed_var = $packed_data; } else { $pack_prefix .= "Eterm $packed_var = $packed_data;\n"; } my $pack = chr(ord('#') + $size); $down = "$pack$down"; } else { $packed_data = arg_offset($size + $offset); $pack_prefix .= "Eterm $packed_var = $packed_data;\n"; $down = "P$down"; $up .= "p"; $this_size = 1; } } $down = "$instr[$ap]$down"; my $unpack = make_unpack($packed_var, $shift[$ap], $mask[$ap]); my $macro = "$reg$unpack_suffix"; $args[$arg_num] = "packed:$reg:$this_size:$macro($unpack)"; $ap++; } else { $skip_unpackable->($reg); } $size += $this_size; $arg_num++; } } # # Skip any unpackable arguments at the end. # while ($arg_num < @args) { $skip_unpackable->($args[$arg_num]); $arg_num++; } my $pack_spec = "$down:$up"; my $score = pack_score($options, @args); return ($score, [$pack_prefix,$pack_spec,@args]); } sub get_pack_parameters { my($name,$args_per_word,$pack_word_size,$wide_mask) = @_; my(@shift,@mask,@instr); my $unpack_suffix = 'b'; if ($wide_mask and $args_per_word > 1) { @shift = ('0', 'BEAM_WIDE_SHIFT'); @mask = ('BEAM_WIDE_MASK', $WHOLE_WORD); @instr = (PACK_CMD_WIDE) x 2; } elsif ($args_per_word == 1) { @shift = ('0'); @mask = ($WHOLE_WORD); @instr = (PACK_CMD_WIDE); } elsif ($args_per_word == 2) { if ($pack_word_size != $wordsize) { # 64-bit word size, pack 32 bits into instruction word. @shift = ('0', 'BEAM_TIGHT_SHIFT'); @mask = ('BEAM_TIGHT_MASK', $WHOLE_WORD); @instr = (PACK_CMD_TIGHT) x 2; } else { # 32/64 bit word size @shift = ('0', 'BEAM_LOOSE_SHIFT'); @mask = ('BEAM_LOOSE_MASK', $WHOLE_WORD); @instr = (PACK_CMD_LOOSE) x 2; } } elsif ($args_per_word == 3) { if ($pack_word_size != $wordsize) { # 64-bit word size, pack 3 register numbers into instruction word. @shift = ('0', 'BEAM_TIGHTEST_SHIFT', '(2*BEAM_TIGHTEST_SHIFT)'); @mask = ('BEAM_TIGHTEST_MASK', 'BEAM_TIGHTEST_MASK', $WHOLE_WORD); @instr = (PACK_CMD_TIGHTEST) x 3; $unpack_suffix = ''; } else { # 32/64 bit word size. @shift = ('0', 'BEAM_TIGHT_SHIFT', '(2*BEAM_TIGHT_SHIFT)'); if ($wordsize == 32) { @mask = ('BEAM_TIGHT_MASK') x 3; } elsif ($wordsize == 64) { @mask = ('BEAM_TIGHT_MASK', 'BEAM_TIGHT_MASK', $WHOLE_WORD); } @instr = (PACK_CMD_TIGHT) x 3; } } elsif ($args_per_word == 4) { # 64 bit word size only. @shift = ('0', 'BEAM_LOOSE_SHIFT', '(2*BEAM_LOOSE_SHIFT)', '(3*BEAM_LOOSE_SHIFT)'); @mask = ('BEAM_LOOSE_MASK', 'BEAM_LOOSE_MASK', 'BEAM_LOOSE_MASK', $WHOLE_WORD); @instr = (PACK_CMD_LOOSE) x 4; } unless (@shift) { error("$name: internal packing error: args_per_word=$args_per_word, " . "pack_word_size=$pack_word_size"); } (\@shift,\@mask,\@instr,$unpack_suffix); } sub pack_score { my($options,@args) = @_; my $size = 0; # Calculate the number of words. foreach (@args) { if (/^packed:[^:]*:(\d+)/) { $size += $1; } else { $size += $arg_size{$_} } } # Less numbers of words give a higher score; for the same number of # words, using PACK_JUMP or PACK_IN_INSTR_WORD gives a lower score. my $score = 1 + $max_spec_operands*($max_spec_operands - $size); if ($options == PACK_IN_INSTR_WORD) { $score += 0; } elsif ($options == PACK_JUMP) { $score += 1; } elsif ($options == (PACK_JUMP|PACK_IN_INSTR_WORD)) { $score += 2; } elsif ($options == 0) { $score += 3; } $score; } sub make_unpack { my($packed_var, $shift, $mask) = @_; my $e = $packed_var; $e = "($e>>$shift)" if $shift; $e .= "&$mask" unless $mask eq $WHOLE_WORD; $e; } sub build_pack_spec { my $pack_spec = shift; return '' if $pack_spec eq ''; my($down,$up) = split /:/, $pack_spec; while ($down =~ /[gfq]$/ and $up =~ /^p/) { $down = substr($down, 0, -1); $up = substr($up, 1); } "$down$up"; } sub quote { local($_) = @_; return "'$_'" if $_ eq 'try'; return "'$_'" if $_ eq 'catch'; return "'$_'" if $_ eq 'receive'; return "'$_'" if $_ =~ /^[A-Z]/; $_; } # # Parse instruction transformations when they first appear. # sub parse_transformation { local($_) = @_; my($orig) = $_; my($from, $to) = split(/\s*=>\s*/); my(@op); my $rest_var; # The source instructions. my(@from) = split(/\s*\|\s*/, $from); foreach (@from) { if (/^(\w+)\((.*?)\)/) { my($name, $arglist) = ($1, $2); $_ = (compile_transform_function($name, split(/\s*,\s*/, $arglist))); } else { (@op) = split; ($rest_var,$_) = compile_transform(1, $rest_var, @op); } } # # Check for a function which should be called to provide the new # instructions if the left-hand side matched. Otherwise there is # an explicit list of instructions. # my @to; if ($to =~ /^(\w+)\((.*?)\)(.*)/) { my($name, $arglist, $garbage) = ($1, $2, $3); if ($garbage =~ /\S/) { error("garbage after call to '$name()'"); } @to = (compile_transform_function($name, split(/\s*,\s*/, $arglist))); } else { @to = split(/\s*\|\s*/, $to); foreach (@to) { (@op) = split; (undef,$_) = compile_transform(0, $rest_var, @op); } } push(@transformations, [$., $orig, [@from], [reverse @to]]); } sub compile_transform_function { my($name, @args) = @_; [".$name", 0, @args]; } sub compile_transform { my($src, $rest_var, $name, @ops) = @_; my $arity = 0; foreach (@ops) { my(@list) = tr_parse_op($src, $_); if ($list[1] eq '*') { $rest_var = $list[0]; } elsif (defined $rest_var and $list[0] eq $rest_var) { $list[1] = '*'; } else { $arity++; } $_ = [ @list ]; } if (defined $gen_opnum{$name,$arity} && $obsolete[$gen_opnum{$name,$arity}]) { error("obsolete function must not be used in transformations"); } if ($src) { $is_transformed{$name,$arity} = 1; } ($rest_var,[$name,$arity,@ops]); } sub tr_parse_op { my($src, $op) = @_; my($var) = ''; my($type) = ''; my($type_val) = 0; my($cond) = ''; my($cond_val) = ''; local($_) = $op; # Get the variable name if any. if (/^([A-Z]\w*)(.*)/) { $var = $1; $_ = $2; error("garbage after variable") unless /^=(.*)/ or /^(\s*)$/; $_ = $1; } # Get the type if any. if (/^([a-z*]+)(.*)/) { $type = $1; $_ = $2; foreach (split('', $type)) { error("bad type in $op") unless defined $type_bit{$_} or $type eq '*'; $_ eq 'r' and error("$op: 'r' is not allowed in transformations") } } # Get an optional condition. (In source.) if (/^==(.*)/) { $cond = 'is_eq'; $cond_val = $1; $_ = ''; } elsif (/^\$is_bif(.*)/) { $cond = 'is_bif'; $cond_val = -1; $_ = $1; } elsif (/^\$is_not_bif(.*)/) { $cond = 'is_not_bif'; $cond_val = -1; $_ = $1; } elsif (m@^\$bif:(\w+):(\w+)/(\d)(.*)@) { $cond = 'is_bif'; if ($1 eq 'erlang') { $cond_val = "BIF_$2_$3"; } else { $cond_val = "BIF_$1_$2_$3"; } $_ = $4; } elsif (m@^\$func:(\w+):(\w+)/([_\d])(.*)@) { my $arity = $3 eq '_' ? 1024 : $3; $cond = 'is_func'; $cond_val = "$1:$2:$arity"; $_ = $4; } # Get an optional value. (In destination.) $type_val = $type eq 'x' ? 1023 : 0; if (/^=(.*)/) { error("value not allowed in source: $op") if $src; $type_val = $1; $_ = ''; } # Nothing more is allowed after the command. error("garbage '$_' after operand: $op") unless /^\s*$/; # Test that destination has no conditions. unless ($src) { error("condition not allowed in destination: $op") if $cond; error("variable name and type cannot be combined in destination: $op") if $var && $type; } ($var,$type,$type_val,$cond,$cond_val); } # # Generate code for all transformations. # sub tr_gen { my(@g) = @_; my($ref, $key, $instr); # Loop variables. foreach $ref (@g) { my($line, $orig_transform, $from_ref, $to_ref) = @$ref; my $so_far = tr_gen_from($line, @$from_ref); tr_gen_to($line, $orig_transform, $so_far, @$to_ref); } # # Print the generated transformation engine. # my($offset) = 0; print "const Uint op_transform[] = {\n"; foreach $key (sort keys %gen_transform) { $gen_transform_offset{$key} = $offset; my @instr = @{$gen_transform{$key}}; # # If the last instruction is 'fail', remove it and # convert the previous 'try_me_else' to 'try_me_else_fail'. # if (is_instr($instr[$#instr], 'fail')) { pop(@instr); my $i = $#instr; $i-- while !is_instr($instr[$i], 'try_me_else'); $instr[$i] = make_op('', 'try_me_else_fail'); } foreach $instr (@instr) { my($size, $instr_ref, $comment) = @$instr; my($op, @args) = @$instr_ref; print " "; if (!defined $op) { $comment =~ s/\n(.)/\n $1/g; print "\n", $comment; } else { $op = "TOP_$op"; $match_engine_ops{$op} = 1; if ($comment ne '') { printf "%-24s /* %s */\n", (join(", ", ($op, @args)) . ","), $comment; } else { print join(", ", ($op, @args)), ",\n"; } $offset += $size; } } print "\n"; } print "/*\n"; print " * Total number of words: $offset\n"; print " */\n"; print "};\n\n"; } sub tr_gen_from { my($line,@tr) = @_; my(%var) = (); my(%var_type); my($var_num) = 0; my(@code); my($op, $ref); # Loop variables. my $where = "left side of transformation in line $line: "; my $may_fail = 0; my $is_first = 1; foreach $ref (@tr) { my($name, $arity, @ops) = @$ref; my($key) = "$name/$arity"; my($opnum); $may_fail = 1 unless $is_first; $is_first = 0; # # A name starting with a period is a C pred function to be called. # if ($name =~ /^\.(\w+)/) { $name = $1; $may_fail = 1; my $var; my(@args); foreach $var (@ops) { error($where, "variable '$var' unbound") unless defined $var{$var}; if ($var_type{$var} eq 'scalar') { push(@args, "var[$var{$var}]"); } else { push(@args, "rest_args"); } } my $pi = tr_next_index(\@pred_table, \%pred_table, $name, @args); my $op = make_op("$name()", 'pred', $pi); my @slots = grep(/^\d+/, map { $var{$_} } @ops); op_slot_usage($op, @slots); push(@code, $op); next; } # # Check that $name/$arity refers to a valid generic instruction. # error($where, "invalid generic op $name/$arity") unless defined $gen_opnum{$name,$arity}; $opnum = $gen_opnum{$name,$arity}; push(@code, make_op("$name/$arity", 'next_instr', $opnum)); foreach $op (@ops) { my($var, $type, $type_val, $cond, $val) = @$op; my $ignored_var = "$var (ignored)"; if ($type ne '' && $type ne '*') { $may_fail = 1; # # The is_bif, is_not_bif, and is_func instructions have # their own built-in type test and don't need to # be guarded with a type test instruction. # $ignored_var = ''; unless ($cond eq 'is_bif' or $cond eq 'is_not_bif' or $cond eq 'is_func') { my($types) = ''; my($type_mask) = 0; foreach (split('', $type)) { $types .= "$_ "; $type_mask |= $type_bit{$_}; } if ($cond ne 'is_eq') { push(@code, make_op($types, 'is_type', $type_mask)); } else { $cond = ''; push(@code, make_op("$types== $val", 'is_type_eq', $type_mask, $val)); } } } if ($cond eq 'is_func') { my($m, $f, $a) = split(/:/, $val); $ignored_var = ''; $may_fail = 1; push(@code, make_op('', "$cond", "am_$m", "am_$f", $a)); } elsif ($cond ne '') { $ignored_var = ''; $may_fail = 1; push(@code, make_op('', "$cond", $val)); } if ($var ne '') { if (defined $var{$var}) { $ignored_var = ''; $may_fail = 1; my $op = make_op($var, 'is_same_var', $var{$var}); op_slot_usage($op, $var{$var}); push(@code, $op); } elsif ($type eq '*') { foreach my $type (values %var_type) { error("only one use of a '*' variable is " . "allowed on the left hand side of " . "a transformation") if $type eq 'array'; } $ignored_var = ''; $var{$var} = 'unnumbered'; $var_type{$var} = 'array'; push(@code, make_op($var, 'rest_args')); } else { $ignored_var = ''; $var_type{$var} = 'scalar'; $var{$var} = $var_num; $var_num++; push(@code, make_op($var, 'set_var', $var{$var})); } } if (is_instr($code[$#code], 'set_var')) { my $ref = pop @code; my $comment = $ref->[2]; my $var = $ref->[1][1]; push(@code, make_op($comment, 'set_var_next_arg', $var)); } else { push(@code, make_op($ignored_var, 'next_arg')); } } # Remove redundant 'next_arg' instructions before the end # of the instruction. pop(@code) while is_instr($code[$#code], 'next_arg'); } # # Insert the commit operation. # push(@code, make_op($may_fail ? '' : 'always reached', 'commit')); $te_max_vars = $var_num if $te_max_vars < $var_num; [\%var, \%var_type, \@code]; } sub tr_gen_to { my($line, $orig_transform, $so_far, @tr) = @_; my($var_ref, $var_type_ref, $code_ref) = @$so_far; my(%var) = %$var_ref; my(%var_type) = %$var_type_ref; my(@code) = @$code_ref; my($op, $ref); # Loop variables. my($where) = "right side of transformation in line $line: "; my $last_instr = $code[$#code]; my $cannot_fail = is_instr($last_instr, 'commit') && (get_comment($last_instr) =~ /^always/); foreach $ref (@tr) { my($name, $arity, @ops) = @$ref; # # A name starting with a period is a C function to be called. # if ($name =~ /^\.(\w+)/) { $name = $1; my $var; my(@args); foreach $var (@ops) { error($where, "variable '$var' unbound") unless defined $var{$var}; if ($var_type{$var} eq 'scalar') { push(@args, "var[$var{$var}]"); } else { push(@args, "rest_args"); } } pop(@code); # Get rid of 'commit' instruction my $index = tr_next_index(\@call_table, \%call_table, $name, @args); my $op = make_op("$name()", 'call_end', $index); my @slots = grep(/^\d+/, map { $var{$_} } @ops); op_slot_usage($op, @slots); push(@code, $op); last; } # # Check that $name/$arity refers to a valid generic instruction. # my($key) = "$name/$arity"; error($where, "invalid generic op $name/$arity") unless defined $gen_opnum{$name,$arity}; my $opnum = $gen_opnum{$name,$arity}; # # Create code to build the generic instruction. # push(@code, make_op("$name/$arity", 'new_instr', $opnum)); foreach $op (@ops) { my($var, $type, $type_val) = @$op; if ($type eq '*') { push(@code, make_op($var, 'store_rest_args')); } elsif ($var ne '') { error($where, "variable '$var' unbound") unless defined $var{$var}; my $op = make_op($var, 'store_var_next_arg', $var{$var}); op_slot_usage($op, $var{$var}); push(@code, $op); } elsif ($type ne '') { push(@code, make_op('', 'store_type', "TAG_$type")); if ($type_val) { push(@code, make_op('', 'store_val', $type_val)); } push(@code, make_op('', 'next_arg')); } } pop(@code) if is_instr($code[$#code], 'next_arg'); } push(@code, make_op('', 'end')) unless is_instr($code[$#code], 'call_end'); tr_maybe_keep(\@code); tr_maybe_rename(\@code); tr_remove_unused(\@code); # # Chain together all codes segments having the same first operation. # my($first_ref) = shift(@code); my($size, $first, $key) = @$first_ref; my($dummy, $arity); ($dummy, $op, $arity) = @$first; my($comment) = "\n/*\n * Line $line:\n * $orig_transform\n */\n\n"; my $prev_last; $prev_last = pop(@{$gen_transform{$key}}) if defined $gen_transform{$key}; # Fail if ($prev_last && !is_instr($prev_last, 'fail')) { error("Line $line: A previous transformation shadows '$orig_transform'"); } unless ($cannot_fail) { unshift(@code, make_op('', 'try_me_else', tr_code_len(@code))); push(@code, make_op("$key", 'fail')); } unshift(@code, make_op($comment)); push(@{$gen_transform{$key}}, @code), } sub tr_maybe_keep { my($ref) = @_; my @last_instr; my $pos; my $reused_instr; for (my $i = 0; $i < @$ref; $i++) { my $instr = $$ref[$i]; my($size, $instr_ref, $comment) = @$instr; my($op, @args) = @$instr_ref; if ($op eq 'next_instr') { @last_instr = ($args[0]); } elsif ($op eq 'set_var_next_arg') { push @last_instr, $args[0]; } elsif ($op eq 'next_arg') { push @last_instr, 'ignored'; } elsif ($op eq 'new_instr') { unless (defined $pos) { # 'new_instr' immediately after 'commit'. $reused_instr = $args[0]; return unless shift(@last_instr) == $reused_instr; $pos = $i - 1; } else { # Second 'new_instr' after 'commit'. The instructions # from $pos up to and including $i - 1 rebuilds the # existing instruction exactly. my $name = $gen_opname[$reused_instr]; my $arity = $gen_arity[$reused_instr]; my $reuse = make_op("$name/$arity", 'keep'); splice @$ref, $pos, $i-$pos, ($reuse); return; } } elsif ($op eq 'store_var_next_arg') { return unless shift(@last_instr) eq $args[0]; } elsif (defined $pos) { return; } } } sub tr_maybe_rename { my($ref) = @_; my $s = 'left'; my $a = 0; my $num_args = 0; my $new_instr; my $first; my $i; for ($i = 1; $i < @$ref; $i++) { my $instr = $$ref[$i]; my($size, $instr_ref, $comment) = @$instr; my($op, @args) = @$instr_ref; if ($s eq 'left') { if ($op eq 'set_var_next_arg') { if ($num_args == $a and $args[0] == $a) { $num_args++; } $a++; } elsif ($op eq 'next_arg') { $a++; } elsif ($op eq 'commit') { $a = 0; $first = $i; $s = 'committed'; } elsif ($op eq 'next_instr') { return; } } elsif ($s eq 'committed') { if ($op eq 'new_instr') { $new_instr = $args[0]; $a = 0; $s = 'right'; } else { return; } } elsif ($s eq 'right') { if ($op eq 'store_var_next_arg' && $args[0] == $a) { $a++; } elsif ($op eq 'end' && $a <= $num_args) { my $name = $gen_opname[$new_instr]; my $arity = $gen_arity[$new_instr]; my $new_op = make_op("$name/$arity", 'rename', $new_instr); splice @$ref, $first, $i-$first+1, ($new_op); return; } else { return; } } } } sub tr_remove_unused { my($ref) = @_; my %used; # Collect all used variables. for my $instr (@$ref) { my $uref = $$instr[3]; for my $slot (@$uref) { $used{$slot} = 1; } } # Replace 'set_var_next_arg' with 'next_arg' if the variable # is never used. for my $instr (@$ref) { my($size, $instr_ref, $comment) = @$instr; my($op, @args) = @$instr_ref; if ($op eq 'set_var_next_arg') { my $var = $args[0]; next if $used{$var}; $instr = make_op("$comment (ignored)", 'next_arg'); } } # Delete a sequence of 'next_arg' instructions when they are # redundant before instructions such as 'commit'. my @opcode; my %ending = (call_end => 1, commit => 1, next_instr => 1, pred => 1, rename => 1, keep => 1); for (my $i = 0; $i < @$ref; $i++) { my $instr = $$ref[$i]; my($size, $instr_ref, $comment) = @$instr; my($opcode) = @$instr_ref; if ($ending{$opcode}) { my $first = $i; $first-- while $first > 0 and $opcode[$first-1] eq 'next_arg'; my $n = $i - $first; splice @$ref, $first, $n; $i -= $n; } $opcode[$i] = $opcode; } } sub tr_code_len { my($sum) = 0; my($ref); foreach $ref (@_) { $sum += $$ref[0]; } $sum; } sub make_op { my($comment, @op) = @_; [scalar(@op), [@op], $comment, []]; } sub op_slot_usage { my($op_ref, @slots) = @_; $$op_ref[3] = \@slots; } sub is_instr { my($ref,$op) = @_; return 0 unless ref($ref) eq 'ARRAY'; $ref->[1][0] eq $op; } sub get_comment { my($ref,$op) = @_; return '' unless ref($ref) eq 'ARRAY'; $ref->[2]; } sub tr_next_index { my($lref,$href,$name,@args) = @_; my $code = "RVAL = $name(" . join(', ', 'st', @args) . "); break;\n"; my $index; if (defined $$href{$code}) { $index = $$href{$code}; } else { $index = scalar(@$lref); push(@$lref, $code); $$href{$code} = $index; } $index; } sub tr_gen_call { my(@call_table) = @_; my($i); for ($i = 0; $i < @call_table; $i++) { print "case $i: $call_table[$i]"; } }