# Copyright (C) 2007, The Perl Foundation. # $Id: PMCEmitter.pm 23961 2007-12-16 10:35:26Z bernhard $ =head1 NAME Parrot::Pmc2c::PMCEmitter - PMC to C Code Generation =head1 SYNOPSIS use Parrot::Pmc2c::PMCEmitter; =head1 DESCRIPTION C is used by F to generate C code from PMC files. =head2 Functions =over =cut package Parrot::Pmc2c::PMC; use strict; use warnings; use Parrot::Pmc2c::Emitter; use Parrot::Pmc2c::Method; use Parrot::Pmc2c::MethodEmitter; use Parrot::Pmc2c::UtilFunctions qw( gen_ret dont_edit count_newlines dynext_load_code c_code_coda ); use Text::Balanced 'extract_bracketed'; use Parrot::Pmc2c::PCCMETHOD; use Parrot::Pmc2c::PMC::RO; use Parrot::Pmc2c::PMC::ParrotClass; sub prep_for_emit { my ( $this, $pmc, $vtable_dump ) = @_; $pmc->vtable($vtable_dump); $pmc->init(); $pmc; } sub generate { my ($self) = @_; my $emitter = $self->{emitter} = Parrot::Pmc2c::Emitter->new( $self->filename(".c") ); $self->generate_c_file; $emitter->write_to_file; $emitter = $self->{emitter} = Parrot::Pmc2c::Emitter->new( $self->filename(".h") ); $self->generate_h_file; $emitter->write_to_file; } =item C Generates the C implementation file code for the PMC. =cut sub generate_c_file { my ($self) = @_; my $c = $self->{emitter}; $c->emit( dont_edit( $self->filename ) ); $c->emit("#define PARROT_IN_EXTENSION\n") if ( $self->is_dynamic ); $self->gen_includes; $c->emit( $self->preamble ); $self->gen_methods; my $ro = $self->ro; if ($ro) { $ro->{emitter} = $self->{emitter}; $ro->gen_methods; } $c->emit( $self->init_func ); $c->emit( $self->postamble ); return 1; } =item C Generates the C header file code for the PMC. =cut sub generate_h_file { my ($self) = @_; my $h = $self->{emitter}; my $name = uc $self->name; $h->emit( dont_edit( $self->filename ) ); $h->emit(<<"EOH"); #ifndef PARROT_PMC_${name}_H_GUARD #define PARROT_PMC_${name}_H_GUARD EOH $h->emit("#define PARROT_IN_EXTENSION\n") if ( $self->is_dynamic ); $h->emit( $self->hdecls ); $h->emit( $self->{ro}->hdecls ) if ( $self->{ro} ); $h->emit(<<"EOH"); #endif /* PARROT_PMC_${name}_H_GUARD */ EOH $h->emit( c_code_coda() ); return 1; } =item C Returns the C code function declarations for all the methods for inclusion in the PMC's C header file. TODO include MMD variants. =cut sub hdecls { my ($self) = @_; my $hout; my $name = $self->name; # generate decls for all vtable methods in this PMC foreach my $vt_method_name ( @{ $self->vtable->names } ) { if ( $self->implements_vtable($vt_method_name) ) { $hout .= $self->get_method($vt_method_name)->generate_headers($self); } } # generate decls for all nci methods in this PMC foreach my $method ( @{ $self->{methods} } ) { next if $method->is_vtable; $hout .= $method->generate_headers($self); } # class init decl $hout .= 'PARROT_DYNEXT_EXPORT ' if ( $self->is_dynamic ); $hout .= "void Parrot_${name}_class_init(PARROT_INTERP, int, int);\n"; $self->{hdecls} .= $hout; $self->{hdecls}; } =back =head2 Instance Methods =over =item C Initializes the instance. =cut sub init { my ($self) = @_; $self->fixup_singleton if $self->singleton; #!( singleton or abstract ) everything else gets readonly version of methods too. $self->ro( Parrot::Pmc2c::PMC::RO->new($self) ) unless ( $self->abstract or $self->singleton ); } sub fixup_singleton { my ($self) = @_; # Since singletons are shared between interpreters, we need to make special effort to use # the right namespace for method lookups. # # Note that this trick won't work if the singleton inherits from something else # (because the MRO will still be shared). unless ( $self->implements_vtable('pmc_namespace') or $self->super_method('pmc_namespace') ne 'default' ) { my $body = Parrot::Pmc2c::Emitter->text( " return INTERP->vtables[SELF->vtable->base_type]->_namespace;\n"); $self->add_method( Parrot::Pmc2c::Method->new( { name => 'pmc_namespace', parent_name => $self->name, parameters => '', body => $body, type => Parrot::Pmc2c::Method::VTABLE, mmds => [], return_type => 'PMC*', attrs => {}, } ) ); } } =item C Returns the C C<#include> for the header file of each of the PMC's superclasses. =cut sub gen_includes { my ($self) = @_; my $c = $self->{emitter}; $c->emit(<<"EOC"); #include "parrot/parrot.h" #include "parrot/extend.h" #include "parrot/dynext.h" EOC $c->emit(qq{#include "pmc_fixedintegerarray.h"\n}) if ( $self->flag('need_fia_header') ); foreach my $parent_name ( $self->name, @{ $self->parents } ) { $c->emit( '#include "pmc_' . lc $parent_name . ".h\"\n" ); } foreach my $mixin_name ( @{ $self->mixins } ) { $c->emit( '#include "pmc_' . lc $mixin_name . ".h\"\n" ); } $c->emit( '#include "' . lc $self->name . ".str\"\n" ) unless $self->is_dynamic; } =item C Determines the prototype (argument signature) for a method body (see F). =cut my %calltype = ( "char" => "c", "short" => "s", "char" => "c", "short" => "s", "int" => "i", "INTVAL" => "I", "float" => "f", "FLOATVAL" => "N", "double" => "d", "STRING*" => "S", "char*" => "t", "PMC*" => "P", "short*" => "2", "int*" => "3", "long*" => "4", "void" => "v", "void*" => "b", "void**" => "B", #"BIGNUM*" => "???" # RT#43731 ); sub proto { my ( $type, $parameters ) = @_; # reduce to a comma separated set of types $parameters =~ s/\w+(,|$)/,/g; $parameters =~ s/ //g; # flatten whitespace before "*" in return value $type =~ s/\s+\*$/\*/ if defined $type; # type method(interp, self, parameters...) my $ret = $calltype{ $type or "void" }; $ret .= "JO" . join( '', map { $calltype{$_} or "?" } split( /,/, $parameters ) ); # RT#43733 # scan src/call_list.txt if the generated signature is available # RT#43735 report errors for "?" # --leo return $ret; } sub pre_method_gen { } =item C Returns the C code for the pmc methods. =cut sub gen_methods { my ($self) = @_; # vtable methods foreach my $method ( @{ $self->vtable->methods } ) { my $vt_method_name = $method->name; next if $vt_method_name eq 'class_init'; if ( $self->implements_vtable($vt_method_name) ) { $self->get_method($vt_method_name)->generate_body($self); } } # non-vtable methods foreach my $method ( @{ $self->methods } ) { next if $method->is_vtable; $method->generate_body($self); } } # RT#43737 quick hack - to get MMD variants sub get_super_mmds { my ( $self, $vt_method_name, $right, $mmd_prefix ) = @_; my @mmds; my $super_mmd_rights = $self->{super_mmd_rights}{$vt_method_name}; if ($super_mmd_rights) { while ( my ( $super_pmc_name, $mmd_rights ) = each %{$super_mmd_rights} ) { for my $x ( @{$mmd_rights} ) { next if $x eq "DEFAULT"; my $right = "enum_class_$x"; my $super_name = "Parrot_${super_pmc_name}_${vt_method_name}_$x"; push @mmds, [ $mmd_prefix, 0, $right, $super_name ]; } } } return @mmds; } =item C Returns three values: The first is an arrayref of <[ mmd_number, left, right, implementation_func]> suitable for initializing the MMD list. The second is a arrayref listing dynamic PMCs which will need to be looked up. The third is a list of C<[index, dynamic PMC]> pairs of right entries in the MMD table that will need to be resolved at runtime. =cut sub find_mmd_methods { my $self = shift; my $pmcname = $self->name; my ( @mmds, @init_mmds, %init_mmds ); foreach my $vt_method ( @{ $self->vtable->methods } ) { my $vt_method_name = $vt_method->name; next unless $vt_method->is_mmd; my $implementor; if ( !$self->implements_vtable($vt_method_name) ) { my $class = $self->{super}{$vt_method_name}; next if $class =~ /^[A-Z]/ or $class eq 'default' or $class eq 'delegate'; $implementor = $class; } else { $implementor = $pmcname; } my ( $mmd_method_name, $func, $left, $right ); $mmd_method_name = "Parrot_${implementor}_$vt_method_name"; $func = $vt_method->mmd_prefix; # dynamic PMCs need the runtime type which is passed in entry to class_init $left = 0; # set to 'entry' below in initialization loop. $right = $vt_method->right; if ( exists $self->{super}{$vt_method_name} ) { push @mmds, $self->get_super_mmds( $vt_method_name, $right, $func ); } push @mmds, [ $func, $left, $right, $mmd_method_name ]; my $pmc_method = $self->get_method($vt_method_name); if ($pmc_method) { foreach my $mmd ( @{ $pmc_method->mmds } ) { my $right = $mmd->right; if ( $self->is_dynamic($right) ) { $right = 0; push @init_mmds, [ $#mmds + 1, $mmd->right ]; $init_mmds{ $mmd->right } = 1; } else { $right = "enum_class_$right"; } $mmd_method_name = "Parrot_" . $self->name . "_" . $mmd->name; push @mmds, [ $func, $left, $right, $mmd_method_name ]; } #$self->{mmds} = @mmds; # RT#43739 } } return ( \@mmds, \@init_mmds, [ keys %init_mmds ] ); } sub build_full_c_vt_method_name { my ( $self, $vt_method_name ) = @_; my $implementor; if ( $self->implements_vtable($vt_method_name) ) { return $self->get_method($vt_method_name) ->full_method_name( $self->name . $self->{variant} ); } elsif ( $self->{super}{$vt_method_name} ) { $implementor = $self->{super}{$vt_method_name}; } else { $implementor = "default"; } return "Parrot_${implementor}_$vt_method_name"; } =item C Returns the C code for the declaration of a vtable temporary named C<$name> with the functions for this class. =cut sub vtable_decl { my ( $self, $temp_struct_name, $enum_name ) = @_; # gen vtable flags my $vtbl_flag = 0; $vtbl_flag .= '|VTABLE_PMC_NEEDS_EXT' if $self->flag('need_ext'); $vtbl_flag .= '|VTABLE_PMC_IS_SINGLETON' if $self->flag('singleton'); $vtbl_flag .= '|VTABLE_IS_SHARED_FLAG' if $self->flag('is_shared'); $vtbl_flag .= '|VTABLE_IS_READONLY_FLAG' if $self->flag('is_ro'); $vtbl_flag .= '|VTABLE_HAS_READONLY_FLAG' if $self->flag('has_ro'); my @vt_methods; foreach my $vt_method ( @{ $self->vtable->methods } ) { next if $vt_method->is_mmd; push @vt_methods, $self->build_full_c_vt_method_name( $vt_method->name ); } my $methlist = join( ",\n ", @vt_methods ); my $cout = < Returns the C code for the PMC's initialization method, or an empty string if the PMC has a C flag. =cut sub init_func { my ($self) = @_; return "" if $self->no_init; my $cout = ""; my $classname = $self->name; my ( $mmds, $init_mmds, $dyn_mmds ) = $self->find_mmd_methods(); my $enum_name = $self->is_dynamic ? -1 : "enum_class_$classname"; my $vtable_decl = $self->vtable_decl( 'temp_base_vtable', $enum_name ); my $mmd_list = join( ",\n ", map { "{ $_->[0], $_->[1], $_->[2], (funcptr_t) $_->[3] }" } @$mmds ); my $isa = join( " ", $classname, @{ $self->parents } ); $isa =~ s/\s?default$//; my $does = join( " ", keys( %{ $self->{flags}{does} } ) ); my $class_init_code = ""; $class_init_code = $self->get_method('class_init')->body if $self->has_method('class_init'); $class_init_code =~ s/INTERP/interp/g; $class_init_code =~ s/^/ /mg; #fix indenting my %extra_vt; if ( $self->{ro} ) { $extra_vt{ro} = $self->{ro}; } $cout .= <<"EOC"; void Parrot_${classname}_class_init(PARROT_INTERP, int entry, int pass) { $vtable_decl EOC for my $k ( keys %extra_vt ) { $cout .= $extra_vt{$k}->vtable_decl( "temp_${k}_vtable", $enum_name ); } my $const = ( $self->{flags}{dynpmc} ) ? " " : " const "; if ( scalar @$mmds ) { $cout .= <<"EOC"; $const MMD_init _temp_mmd_init[] = { $mmd_list }; /* Dynamic PMCs need the runtime type which is passed in entry to class_init. */ EOC } $cout .= <<"EOC"; if (pass == 0) { EOC $cout .= <<"EOC"; /* create vtable - clone it - we have to set a few items */ VTABLE *vt_clone = Parrot_clone_vtable(interp, &temp_base_vtable); EOC for my $k ( keys %extra_vt ) { $cout .= <<"EOC"; VTABLE *vt_${k}_clone = Parrot_clone_vtable(interp, &temp_${k}_vtable); EOC } # init vtable slot if ( $self->is_dynamic ) { $cout .= <<"EOC"; vt_clone->base_type = entry; vt_clone->whoami = string_make(interp, "$classname", @{[length($classname)]}, "ascii", PObj_constant_FLAG|PObj_external_FLAG); vt_clone->isa_str = string_make(interp, "$isa", @{[length($isa)]}, "ascii", PObj_constant_FLAG|PObj_external_FLAG); vt_clone->does_str = string_make(interp, "$does", @{[length($does)]}, "ascii", PObj_constant_FLAG|PObj_external_FLAG); EOC } else { $cout .= <<"EOC"; vt_clone->whoami = CONST_STRING(interp, "$classname"); vt_clone->isa_str = CONST_STRING(interp, "$isa"); vt_clone->does_str = CONST_STRING(interp, "$does"); EOC } for my $k ( keys %extra_vt ) { $cout .= <<"EOC"; vt_${k}_clone->base_type = entry; vt_${k}_clone->whoami = vt_clone->whoami; vt_${k}_clone->isa_str = vt_clone->isa_str; vt_${k}_clone->does_str = vt_clone->does_str; EOC } if ( $extra_vt{ro} ) { $cout .= <<"EOC"; vt_clone->ro_variant_vtable = vt_ro_clone; vt_ro_clone->ro_variant_vtable = vt_clone; EOC } $cout .= <<"EOC"; interp->vtables[entry] = vt_clone; EOC $cout .= <<"EOC"; } else { /* pass */ EOC # To make use of the .HLL directive, register any mapping... if ( $self->{flags}{hll} && $self->{flags}{maps} ) { my $hll = $self->{flags}{hll}; $cout .= <<"EOC"; { /* Register this PMC as a HLL mapping */ INTVAL pmc_id = Parrot_get_HLL_id( interp, const_string(interp, "$hll") ); if (pmc_id > 0) { EOC foreach my $maps ( sort keys %{ $self->{flags}{maps} } ) { $cout .= <<"EOC"; Parrot_register_HLL_type( interp, pmc_id, enum_class_$maps, entry); EOC } $cout .= <<"EOC"; } } /* Register */ EOC } $cout .= <<"EOC"; /* setup MRO and _namespace */ Parrot_create_mro(interp, entry); EOC # declare each nci method for this class foreach my $method ( @{ $self->{methods} } ) { next unless $method->type eq Parrot::Pmc2c::Method::NON_VTABLE; my $proto = proto( $method->return_type, $method->parameters ); my $method_name = $method->name; my $symbol_name = defined $method->symbol ? $method->symbol : $method->name; if ( exists $method->{PCCMETHOD} ) { $cout .= <<"EOC"; register_raw_nci_method_in_ns(interp, entry, F2DPTR(Parrot_${classname}_${method_name}), "$symbol_name"); EOC } else { $cout .= <<"EOC"; register_nci_method(interp, entry, F2DPTR(Parrot_${classname}_${method_name}), "$symbol_name", "$proto"); EOC } if ( $method->{attrs}{write} ) { $cout .= <<"EOC"; Parrot_mark_method_writes(interp, entry, "$symbol_name"); EOC } } # include any class specific init code from the .pmc file $cout .= <<"EOC"; /* class_init */ EOC $cout .= <<"EOC" if $class_init_code; { $class_init_code } EOC $cout .= <<"EOC"; { EOC # declare auxiliary variables for dyncpmc IDs foreach my $dynpmc (@$dyn_mmds) { next if $dynpmc eq $classname; $cout .= <<"EOC"; int my_enum_class_$dynpmc = pmc_type(interp, string_from_literal(interp, "$dynpmc")); EOC } # init MMD "right" slots with the dynpmc types foreach my $entry (@$init_mmds) { if ( $entry->[1] eq $classname ) { $cout .= <<"EOC"; _temp_mmd_init[$entry->[0]].right = entry; EOC } else { $cout .= <<"EOC"; _temp_mmd_init[$entry->[0]].right = my_enum_class_$entry->[1]; EOC } } # just to be safe foreach my $dynpmc (@$dyn_mmds) { next if $dynpmc eq $classname; $cout .= <<"EOC"; PARROT_ASSERT(my_enum_class_$dynpmc != enum_class_default); EOC } if ( scalar @$mmds ) { $cout .= <<"EOC"; #define N_MMD_INIT (sizeof(_temp_mmd_init)/sizeof(_temp_mmd_init[0])) Parrot_mmd_register_table(interp, entry, _temp_mmd_init, N_MMD_INIT); EOC } $cout .= <<"EOC"; } } /* pass */ } /* Parrot_${classname}_class_init */ EOC if ( $self->is_dynamic ) { $cout .= dynext_load_code( $classname, $classname => {} ); } $cout; } sub is_vtable_method { my ( $self, $vt_method_name ) = @_; return 1 if $self->vtable->has_method($vt_method_name); return 0; } sub vtable { my ( $self, $value ) = @_; $self->{vtable} = $value if $value; return $self->{vtable}; } 1; # Local Variables: # mode: cperl # cperl-indent-level: 4 # fill-column: 100 # End: # vim: expandtab shiftwidth=4: