From f51cb2d9798f8a0b00d961a0fca8b12a8979e8c5 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Tue, 1 Feb 2022 11:25:47 +1030 Subject: [PATCH] Partial move to new generator. --- api/Makefile | 18 + {test-api => api}/api.c | 0 api/api.h | 88 +++ src/apigen.pm | 602 ++++++++++++++++++ src/code.api | 339 ++++++++++ src/code.pm | 294 +++++++++ src/export-defines | 65 +- src/export.cc | 107 +++- src/generate-api-2 | 390 ++++++++++++ src/method.pm | 156 +++++ src/tokenise.pm | 135 ++++ src/types.api | 256 ++++++++ test-api-object/Makefile | 44 ++ {test-api => test-api-object}/README | 0 test-api-object/api-object.api | 46 ++ test-api-object/src/api/test/TestAPI.java | 68 ++ test-api-static/Makefile | 46 ++ test-api-static/README | 8 + test-api-static/api-static.api | 25 + .../src/api/test/TestAPI.java | 8 +- test-api/Makefile | 55 -- test-api/api.api | 15 - test-api/api.h | 39 -- 23 files changed, 2641 insertions(+), 163 deletions(-) create mode 100644 api/Makefile rename {test-api => api}/api.c (100%) create mode 100644 api/api.h create mode 100644 src/apigen.pm create mode 100644 src/code.api create mode 100644 src/code.pm create mode 100755 src/generate-api-2 create mode 100644 src/method.pm create mode 100644 src/tokenise.pm create mode 100644 src/types.api create mode 100644 test-api-object/Makefile rename {test-api => test-api-object}/README (100%) create mode 100644 test-api-object/api-object.api create mode 100644 test-api-object/src/api/test/TestAPI.java create mode 100644 test-api-static/Makefile create mode 100644 test-api-static/README create mode 100644 test-api-static/api-static.api rename {test-api => test-api-static}/src/api/test/TestAPI.java (92%) delete mode 100644 test-api/Makefile delete mode 100644 test-api/api.api delete mode 100644 test-api/api.h diff --git a/api/Makefile b/api/Makefile new file mode 100644 index 0000000..fbe6563 --- /dev/null +++ b/api/Makefile @@ -0,0 +1,18 @@ + +CFLAGS=-g -fPIC + +all:: + mkdir -p bin + +all:: bin/libapi.so + +bin/api.o: api.c api.h + $(CC) $(CFLAGS) -c -o $@ $< + +bin/libapi.so: bin/api.o + $(CC) -o $@ -shared $^ + +clean: + rm -rf bin + +.PHONY: clean all diff --git a/test-api/api.c b/api/api.c similarity index 100% rename from test-api/api.c rename to api/api.c diff --git a/api/api.h b/api/api.h new file mode 100644 index 0000000..50356a0 --- /dev/null +++ b/api/api.h @@ -0,0 +1,88 @@ + +#define API_FOO "12" +#define API_MAKEVERSION(a, b) (a <<16) | b +#define API_VERSION API_MAKEVERSION(1, 0) +#define API_A 12UL +#define API_B 12L +#define API_C 12U +#define API_D 12.0 +#define API_E 12.0f + +enum api_enum { + OK, + NOT_OK +}; + +union number { + int int32; + float float32; +}; + +struct foo { + int a; + int b; + struct anon *c; +}; + +typedef void (*foo_fn1)(float x, float y); +typedef float (*foo_fn2)(float x, float y); + +struct data { + struct data *next; + + int a; + int b; + int c:3; + unsigned d:5; + int (*test_a)(void); + + enum api_enum status; +#if 0 + int (*test_b)(int a, int b, int c); + int (*test_c)(int d, int (*foo)(int e, int f), int g); + void (*test_d)(void); + + foo_fn test_e; + void (*test_f)(foo_fn h); + + + char array1d[12]; + char array2d[12][3]; + + struct foo foo; +#endif +}; + +void print_data(struct data *data); + +struct api { + void (*funca)(int a); + int (*funcb)(int b); + int (*funcc)(float b); +}; + +void *api_func(const char *name); +void api_free(struct api *api); +struct api *api_create(void); +void api_void(struct api *, int *); + +void api_data(struct api *, int size, void *data); + +// constructor which returns object +struct api *api_new_a(void); +// constructor which returns object, rc via pointer +struct api *api_new_b(int *rc); +// constructor which returns value in pointer-holder, and error via return code +int api_new_c(struct api **apip); +// constructor which returns value in pointer-holder, error via ? +void api_new_d(struct api **apip); +// constructor which returns value in pointer-holder, and error via return code holder +void api_new_e(struct api **apip, int *rc); + +/* +struct list_node { + struct list_node *succ; + struct list_node *pred; + char name[64-16]; +}; +*/ diff --git a/src/apigen.pm b/src/apigen.pm new file mode 100644 index 0000000..afb5cda --- /dev/null +++ b/src/apigen.pm @@ -0,0 +1,602 @@ + +package apigen; + +use strict; +use Data::Dumper; +require genconfig; + +my %renameTable = ( + 'studly-caps' => sub { my $s = shift; $s =~ s/(?:^|_)(.)/\U$1/g; return $s; }, + 'camel-case' => sub { my $s = shift; $s =~ s/(?:_)(.)/\U$1/g; return $s; }, + 'upper-leadin' => sub { my $s = shift; $s =~ s/^([^_]+)/\U$1/; return $s; }, + 'identity' => sub { return $_[0]; }, + 'call' => sub { + my $s = shift; + + if ($s =~ m/\(/) { + $s =~ s/u32:|u64:/p/g; + $s =~ s/\$\{([^\}]+)\}/$1/g; + $s =~ s/[\(\)]/_/g; + $s =~ s/^/Call/; + } + $s; + } + +); + +my %defaultTable = ( + 'struct:' => { + name => '', + items => [], + options => [ 'default=none', 'access=rw' ], + regex => qr/^struct:$/, + type => 'struct' + }, + 'union:' => { + name => '', + items => [], + options => [ 'default=none', 'access=rw' ], + regex => qr/^union:$/, + type => 'union' + }, + 'call:' => { + name => '', + items => [], + options => [ 'rename=call', 'access=r' ], + regex => qr/^call:$/, + type => 'call' + }, +); + +# hmm, so do i iterate 'control' and find matches in 'api' +# or do i iterate 'api' and find matches in 'control'? + +sub new { + my $class = shift; + my $file = shift; + my $self = { }; + + $self->{api} = genconfig::loadControlFile($file); + $self->{index} = {}; + foreach my $obj (@{$self->{api}}) { + $self->{index}->{"$obj->{type}:$obj->{name}"} = $obj; + } + foreach my $k (keys %defaultTable) { + $self->{index}->{$k} = $defaultTable{$k} if !defined($self->{index}->{$k}); + } + + $self->{data} = {}; + while ($#_ >= 0) { + my $name = shift; + my $info = loadAPIFile($name); + + $self->{data} = { %{$self->{data}}, %{$info} }; + } + + analyseAPI($self); + preprocess($self); + + # add phantom 'api' entries for anything + foreach my $s (findDependencies($self)) { + my $n = "$s->{type}:$s->{name}"; + my $def = $self->{index}->{"$s->{type}:"}; + + die "no default for implicit dependency $n" if (!$def); + + my $obj = { + name => $s->{name}, + items => $def->{items}, + options => $def->{options}, + regex => qr/^$n$/, + type => $def->{type} + }; + + print " implicit $n\n"; + push @{$self->{api}}, $obj; + $self->{index}->{$n} = $obj; + } + + postprocess($self); + + bless $self, $class; + + return $self; +} + +sub renameFunction { + my $api = shift; + my $name = shift; + $renameTable{$name}; +} + +# find data objects matching top-level data objects +# return [ (obj, def) ] pairs in a list +# if there are multiple matches only return the first +sub findObjects { + my $api = shift; + my $type = shift; + my @list; + my %seen = (); + + #print Dumper($api->{data}); + + foreach my $obj (grep { $_->{type} eq $type} @{$api->{api}}) { + my $s = $api->{data}->{"$type:$obj->{name}"}; + print "? $obj->{name} regex $obj->{regex} s=$s\n"; + if ($s) { + next if $seen{$s->{name}}++; + push @list, [ $obj, $s ]; + } else { + foreach my $k (grep { $_ =~ $obj->{regex} } keys(%{$api->{data}})) { + $s = $api->{data}->{$k}; + next if $seen{$s->{name}}++; + #print "+ $k\n"; + push @list, [ $obj, $s ]; + } + } + } + + return @list; +} + +sub findAllObjects { + my $api = shift; + my @list; + + foreach my $obj (@{$api->{api}}) { + print "? $obj->{name}\n"; + foreach my $dat (grep { $_->{name} =~ $obj->{regex} } values(%{$api->{data}})) { + #print "+ $dat->{name}\n"; + push @list, [ $obj, $dat ]; + } + } + + return @list; +} + +# TODO: check these next few against the one in generate-api, + +# find option(s) in an obj or inc +sub option { + my $obj = shift; + my $name = shift; + my $rx = qr/^$name=/; + + return grep { $_ =~ m/$rx/ } @{$obj->{options}}; +} + +# find value of first option of the given name +sub optionValue { + my $name = shift; + my $or = shift; + my $rx = qr/$name/; + + foreach my $obj (@_) { + foreach my $opt (@{$obj->{options}}) { + return $1 if ($opt =~m/^$rx=(.*)/); + } + } + $or; +} + +# look for all matching options of the given name +# multiple objects are searched, the first one with +# the given parameter overrides the rest. +# name, object, object * +sub optionValues { + my $name = shift; + my $rx = qr/$name/; + my @list; + + foreach my $obj (@_) { + foreach my $opt (@{$obj->{options}}) { + push @list, $1 if ($opt =~m/^$rx=(.*)/); + } + last if ($#list >= 0); + } + @list; +} + +# find first occurance of a flag +sub optionFlag { + my $name = shift; + + foreach my $obj (@_) { + foreach my $opt (@{$obj->{options}}) { + return 1 if ($opt eq $name); + } + } + 0; +} + +# find live/desired items of a given type under a particular object +# returns list of [ $inc, $field/argument ] +sub findItems { + my $api = shift; + my $obj = shift; + my $s = shift; + my $mode = shift; + my %visited = (); + my @fields = (); + + #print Dumper($s); + + my @all = @{$s->{items}}; + my %index; + + foreach my $m (@all) { + $index{$m->{name}} = $m; + } + + #print "find mode $mode in $s->{name} $s->{type}\n"; + #print Dumper($s); + + foreach my $inc (grep { $_->{mode} eq $mode } @{$obj->{items}}) { + my $d = $index{$inc->{match}}; + + if ($d) { + next if $visited{$d->{type}.':'.$d->{name}}++; + push @fields, [ $inc, $d ]; + } else { + foreach my $d (grep { $_->{name} =~ m/$inc->{regex}/ } @all) { + next if $visited{$d->{type}.':'.$d->{name}}++; + push @fields, [ $inc, $d ]; + } + } + } + + # FIXME: rather than all, use 'struct:' options for fallback + if (optionValue('default', 'all', $obj) eq 'all') { + foreach my $d (@all) { + next if $visited{$d->{type}.':'.$d->{name}}++; + # TODO: other things might need moving over + my $inc = {}; + $inc->{"$mode:rename"} = $obj->{"$mode:rename"} if $obj->{"$mode:rename"}; + push @fields, [ $inc, $d ]; + } + } + + return @fields; +} + +sub findAllItems { + my $api = shift; + my $obj = shift; + my $s = shift; + my %visited = (); + my @fields = (); + + #print Dumper($obj); + + my @all = @{$s->{items}}; + my %index; + + foreach my $m (@all) { + $index{$m->{name}} = $m; + } + + foreach my $inc (@{$obj->{items}}) { + my $d = $index{$inc->{match}}; + + if ($d) { + next if $visited{$d->{type}.':'.$d->{name}}++; + push @fields, [ $inc, $d ]; + } else { + foreach my $d (grep { $_->{name} =~ m/$inc->{regex}/ } @all) { + next if $visited{$d->{type}.':'.$d->{name}}++; + push @fields, [ $inc, $d ]; + } + } + } + + if (optionValue('default', 'all', $obj) eq 'all') { + #print "* add all items\n"; + foreach my $d (@all) { + next if $visited{$d->{type}.':'.$d->{name}}++; + push @fields, [ $obj, $d ]; + } + } + + return @fields; +} + +# ###################################################################### + +sub addDependencies { + my $api = shift; + my $obj = shift; + my $s = shift; + my $add = shift; + + #print "add deps for '$s->{name}'\n"; + if ($s->{type} =~ m/^(struct|union)$/) { + # include embedded structures always + foreach my $d (grep { $_->{type} =~ m/^(struct|union):/ && $_->{deref} =~ m/\[\d+\$\{|^\$\{/ } @{$s->{items}}) { + #print " embedded $d->{name} $d->{deref}\n"; + $add->($d->{type}); + } + + # include selected fields optionally + if ($obj) { + foreach my $i (findAllItems($api, $obj, $s)) { + my ($inc, $d) = @{$i}; + #print " selected $d->{name} $d->{type} $d->{deref}\n"; + $add->($d->{type}) if ($d->{type} =~ m/^(struct|union|func|call|enum):/); + } + } + } elsif ($s->{type} =~ m/^(call|func)/) { + # for calls/func need all fields + foreach my $d (grep { $_->{type} =~ m/^(struct|union|func|call|enum):/ } @{$s->{items}}, $s->{result}) { + #print " argument $d->{name} $d->{type} $d->{deref}\n"; + $add->($d->{type}); + } + } +} + +# find all extra types used by the api requested +sub findDependencies { + my $api = shift; + my %data = %{$api->{data}}; + my %seen; + my %deps; + my $setdeps = sub { my $d = shift; $deps{$d} = 1; }; + + foreach my $obj (@{$api->{api}}) { + if ($obj->{type} eq 'library') { + foreach my $inc (@{$obj->{items}}) { + print "? $inc->{regex}\n"; + foreach my $n (grep { $_ =~ m/$inc->{regex}/ } keys %data) { + my $s = $data{$n}; + + print "+ $n\n"; + + $seen{$n}++; + addDependencies($api, $obj, $s, $setdeps); + } + } + } elsif ($obj->{type} =~ m/^(struct|union|call|func|enum|define)$/) { + foreach my $n (grep { $_ =~ m/$obj->{regex}/ } keys %data) { + my $s = $data{$n}; + + $seen{$n}++; + addDependencies($api, $obj, $s, $setdeps); + } + } + } + + # at this point 'seen' contains everything explicitly requested + # and deps is anything else they need but not referenced directly + # recursively grab anything else + + my @list = (); + my @stack = sort keys %deps; + my $pushstack = sub { my $d = shift; push @stack, $d; }; + while ($#stack >= 0) { + my $n = shift @stack; + my $s; + + next if $seen{$n}++; + + $s = $data{$n}; + + if ($s) { + print "Add referent: $n\n"; + addDependencies($api, $api->{index}->{"$s->{type}:"}, $s, $pushstack); + } elsif ($n =~ m/^(.*):(.*)$/) { + print "Add anonymous: $n\n"; + # type not know, add anonymous + $s = { + name => $2, + type => $1, + size => 0, + items => [], + }; + $api->{data}->{$n} = $s; + } + + # maybe it should have some skeleton metadata? + # depends on where it's used i suppose + push @list, $s; + } + + print "Added ".($#list+1)." dependencies\n"; + return @list; +} + +# ###################################################################### + +sub loadAPIFile { + my $file = shift; + my $info; + + unless ($info = do $file) { + die "couldn't parse $file: $@" if $@; + die "couldn't import $file: $!" unless defined $info; + die "couldn't run $file" unless $info; + } + + return $info; +} + +sub parseRename { + my $how = shift; + my $rename = $renameTable{'identity'}; + + foreach my $n (split /,/,$how) { + my $old = $rename; + my $new = $renameTable{$n}; + + if ($n =~ m@^s/(.*)/(.*)/$@) { + my $rx = qr/$1/; + my $rp = $2; + $rename = sub { my $s=shift; $s = $old->($s); $s =~ s/$rx/$rp/; return $s;}; + } elsif ($new) { + $rename = sub { my $s=shift; $s = $old->($s); return $new->($s); }; + } else { + my $x = $n; + $rename = sub { return $x; }; + } + } + $rename; +} + +# take a signature-name and convert it to mangled java name +sub renameCall { + my $new = shift; + + if ($new =~ m/\(/) { + $new =~ s/u32:|u64:/p/g; + $new =~ s/\$\{([^\}]+)\}/$1/g; + $new =~ s/[\(\)]/_/g; + $new =~ s/^/Call/; + } + $new; +} + +# pre-process {data} +sub preprocess { + my $api = shift; + + # Find any anonymous types and add them in + my %anonymous = (); + foreach my $s (values %{$api->{data}}) { + # FIXME: just do this in export.cc and export-defines + $s->{items} = $s->{fields} if $s->{fields}; + $s->{items} = $s->{arguments} if $s->{arguments}; + $s->{items} = $s->{values} if $s->{values}; + + foreach my $m (grep { $_->{type} =~ m/struct:|union:/} @{$s->{items}}) { + $anonymous{$m->{type}} = 1 if !defined($api->{data}->{$m->{type}}); + } + } + + foreach my $k (sort keys %anonymous) { + print " anon $k\n"; + if ($k =~ m/^(.*):(.*)$/) { + $api->{data}->{$k} = { + name => $2, + type => $k, + size => 0, + items => [], + }; + } + } + + my $rename = {}; + foreach my $i (findObjects($api, 'struct')) { + my ($obj, $dat) = @{$i}; + + if ($obj->{rename}) { + my $old = $dat->{name}; + my $new = $obj->{rename}->($old); + my $oldkey = $obj->{type}.':'.$old; + my $newkey = $obj->{type}.':'.$new; + + $rename->{$oldkey} = $newkey; + } + } + + $api->{rename} = $rename; +} + +# preprocess {api} +sub analyseAPI { + my $api = shift; + + # Note that type:name regexes always start at the beginning + + foreach my $obj (@{$api->{api}}) { + if ($obj->{name} =~ m@^/(.*)/$@) { + $obj->{regex} = qr/^$obj->{type}:$1/; + } else { + $obj->{regex} = qr/^$obj->{type}:$obj->{name}$/; + } + + foreach my $opt (@{$obj->{options}}) { + if ($opt =~ m/^(.*:?rename)=(.*)$/) { + $obj->{$1} = parseRename($2); + } + } + + my $defmode = ($obj->{type} eq 'library' ? 'func' : 'field'); + foreach my $inc (@{$obj->{items}}) { + my $match = $inc->{match}; + my $mode = $defmode; + + if ($inc->{match} =~ m/^(.*):(.*)$/) { + $match = $2; + $mode = $1; + } + + $inc->{mode} = $mode; + if ($match =~ m@^/(.*)/$@) { + $inc->{regex} = $mode ne 'field' ? qr/$mode:$1/ : qr/$1/; + } else { + $inc->{regex} = $mode ne 'field' ? qr/^$mode:$match$/ : qr/^$match$/; + } + + foreach my $opt (@{$inc->{options}}) { + if ($opt =~ m/^rename=(.*)$/) { + #print "option $opt ".Dumper($inc); + $inc->{"$mode:rename"} = parseRename($1); + } + } + + $inc->{"$mode:rename"} = $obj->{"$mode:rename"} if (!(defined($inc->{"$mode:rename"})) && defined($obj->{"$mode:rename"})); + } + } +} + +# transfer info from {api} to {data} +sub postprocess { + my $api = shift; + my $seen = {}; + my %data = %{$api->{data}}; + + # apply func stuff + foreach my $obj (grep {$_->{type} eq 'func'} @{$api->{api}}) { + my @list; + + if ($api->{data}->{$obj->{name}}) { + push @list, ${data}{$obj->{name}}; + } else { + push @list, map { $data{$_} } grep { $_ =~ m/$obj->{regex}/ } keys %data; + } + + foreach my $s (@list) { + my $isconstructor = optionFlag('constructor', $obj); + my $isstatic = optionFlag('static', $obj); + + $s->{constructor} = $isconstructor if $isconstructor; + $s->{static} = $isstatic if $isstatic; + $s->{rename} = $obj->{"$obj->{type}:rename"} ? $obj->{"$obj->{type}:rename"}->($s->{name}) : $s->{name}; + + foreach my $inc (@{$obj->{items}}) { + my @items; + + my $array = optionFlag('array', $inc, $obj); + my $arraysize = optionValue('array-size', undef, $inc); + my $instance = optionFlag('instance', $inc); + my $success = optionValue('success', undef, $inc); + + if ($inc->{match} eq '') { + push @items, $s->{result}; + } else { + @items = grep { $_->{name} =~ m/$inc->{regex}/ } @{$s->{items}}; + } + + foreach my $m (@items) { + $m->{array} = $array if $array; + $m->{arraysize} = $arraysize if defined($arraysize); + $m->{instance} = $instance if $instance; + $m->{success} = $success if defined($success); + } + } + print Dumper({func=>$s, obj=>$obj}); + } + + } + +} + +1; diff --git a/src/code.api b/src/code.api new file mode 100644 index 0000000..884866a --- /dev/null +++ b/src/code.api @@ -0,0 +1,339 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# method template +# 'invoke' is a simple string template +# variables are defined by method.pm +code method { + # normal function invocation + invoke {{ + static final MethodHandle {name}$FH = Memory.downcall("{name}", {function-descriptor}); + public {static}{java-result} {rename}({java-arguments}) { + {native-output-define} + {native-result-define} + try {create-frame}{ + {native-output-init} + {native-result-assign}{name}$FH.invokeExact({native-call}); + {native-output-copy} + {result-test}{ + {java-result-assign} + {on-success} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} + + # callback function/types + downcall {{ + public static Memory.FunctionPointer<{rename}> downcall(MemoryAddress addr$, ResourceScope scope$) { + NativeSymbol symbol$ = NativeSymbol.ofAddress("{rename}", addr$, scope$); + MethodHandle {rename}$FH = Memory.downcall(symbol$, descriptor()); + return new Memory.FunctionPointer<{rename}>( + symbol$, + ({java-arguments}) -> { + {native-output-define} + {native-result-define} + try {create-frame}{ + {native-output-init} + {native-result-assign}{rename}$FH.invokeExact({native-call}); + {native-output-copy} + {result-test}{ + {java-result-assign} + {on-success} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + }} + + upcall {{ + public static Memory.FunctionPointer<{rename}> upcall({rename} target$, ResourceScope scope$) { + interface Trampoline { + {java-result} call({native-arguments}); + } + Trampoline trampoline = ({native-arguments}) -> { + // frame? scope? + {trampoline-result-define}target$.call({java-call}); + {trampoline-result-return} + }; + return new Memory.FunctionPointer<>( + Memory.upcall( + trampoline, + "call", + "{java-signature}", + descriptor(), + scope$), + target$); + } + }} +} + + +# structs - normal structs +# handle - anonymous structs +# library - library template + +code class { + library {{ +package {package}; +import jdk.incubator.foreign.*; +import java.lang.invoke.*; +import {package}.Memory.*; + +public class {name} { +{defines} +{enums} +{funcs} +} + }} + constants {{ +package {package}; + +public interface {name} { +{enums} +{defines} +} + }} + struct {{ +package {package}; +import jdk.incubator.foreign.*; +import jdk.incubator.foreign.MemoryLayout.*; +import java.lang.invoke.*; +import {package}.Memory.*; + +public class {rename} implements Memory.Addressable { + + public final MemorySegment segment; + + private {rename}(MemorySegment segment) { + this.segment = segment; + } + + public static {rename} create(MemorySegment segment) { + return new {rename}(segment); + } + + public static {rename} create(MemoryAddress address, ResourceScope scope) { + return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)) : null; + } + + public static {rename} create(SegmentAllocator frame) { + return create(frame.allocate(LAYOUT)); + } + + @Override + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public final ResourceScope scope() { + return segment.scope(); + } + + public final MemorySegment segment() { + return segment; + } + +{defines} +{enums} +{accessors} +{methods} +{layout} +{varhandles} +} +}} + struct-array {{ +package {package}; +import jdk.incubator.foreign.*; +import jdk.incubator.foreign.MemoryLayout.*; +import java.lang.invoke.*; +import {package}.Memory.*; + +public class {rename} implements Memory.Addressable, Memory.Array<{rename}> { + + public final MemorySegment segment; + + private {rename}(MemorySegment segment) { + this.segment = segment; + } + + public static {rename} create(MemorySegment segment) { + return new {rename}(segment); + } + + public static {rename} create(MemoryAddress address, ResourceScope scope) { + return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)) : null; + } + + public static {rename} create(SegmentAllocator frame) { + return create(frame.allocate(LAYOUT)); + } + + public static {rename} createArray(MemoryAddress address, ResourceScope scope) { + return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope)) : null; + } + + @Override + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public final ResourceScope scope() { + return segment.scope(); + } + + public final MemorySegment segment() { + return segment; + } + + @Override + public long length() { + return segment.byteSize() / LAYOUT.byteSize(); + } + + @Override + public {rename} getAtIndex(long index) { + try { + return {rename}.create((MemorySegment){name}$SH.invokeExact(segment, index)); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + +{defines} +{enums} +{accessors} +{methods} +{layout} + final static MethodHandle {name}$SH = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(PathElement.sequenceElement()); +{varhandles} +} +}} + handle {{ +package {package}; +import jdk.incubator.foreign.*; +import java.lang.invoke.*; +import {package}.Memory.*; + +public class {rename} implements Memory.Addressable { + + MemoryAddress address; + ResourceScope scope; + + private {rename}(MemoryAddress address, ResourceScope scope) { + this.address = address; + this.scope = scope; + } + + public static {rename} create(MemoryAddress address, ResourceScope scope) { + return MemoryAddress.NULL != address ? new {rename}(address, scope) : null; + } + + @Override + public MemoryAddress address() { + return address; + } + + @Override + public ResourceScope scope() { + return scope; + } + +{defines} +{enums} +{methods} +} +}} + call {{ +package {package}; +import jdk.incubator.foreign.*; +import java.lang.invoke.*; +import {package}.Memory.*; + +@FunctionalInterface +public interface {rename} { + {java-result} call({java-arguments}); + + public static FunctionDescriptor descriptor() { + return {function-descriptor}; + } + +{upcall} +{downcall} +} +}} +} + +# do I want this to be perl code as template or just a template? +code getset { + get set=value={getnative} {{ + public {type} get{rename}() { + return {tojava}; + } + }} + geti set=value={getnative} set=segment=segment {{ + public {type} get{rename}AtIndex(long index) { + // option a: resolve an offset segment and asme as above with set=segment=segment + // option b: an indexed varhandle + MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize()); + return {tojava}; + } + }} + set set=value=value {{ + public void set{rename}({type} value) { + {setnative}; + } + }} + seti set=value=value set=segment=segment {{ + public void set{rename}AtIndex(long index, {type} value) { + MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize()); + {setnative}; + } + }} +} + +code getsetelement { + get set=index=i {{ + public {typei} get{rename}Element(long i) { + return {getnativei}; + } + }} + set set=index=i set=value=value {{ + public void set{rename}Element(long i, {typei} value) { + {setnativei}; + } + }} +} + +code getsetelement2d { + get set=index0=i set=index1=j {{ + public {typei} get{rename}Element(long i, long j) { + return {getnativei}; + } + }} + set set=index0=i set=index1=j set=value=value {{ + public void set{rename}Element(long i, long j, {typei} value) { + {setnativei}; + } + }} +} + +code getbyvalue { + get {{ + public {type} get{rename}() { + try { + return {getnative}; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }} +} diff --git a/src/code.pm b/src/code.pm new file mode 100644 index 0000000..156aeee --- /dev/null +++ b/src/code.pm @@ -0,0 +1,294 @@ +package code; + +use strict; + +use File::Path qw(make_path); +use File::Basename; +use Data::Dumper; +use List::Util qw(first); + +require genapi; + +my %typeSizes = ( + i8 => 'byte', u8 => 'byte', + i16 => 'short', u16 => 'short', + i32 => 'int', u32 => 'int', + i64 => 'long', u64 => 'long', + f32 => 'float', + f64 => 'double', +); + +my %typeSuffix = ( + 'long' => 'L', + 'float' => 'f' +); + +my %defineType = ( + %typeSizes, + string => 'String' +); + +my %typePrefix = ( + i8 => '(byte)', + u8 => '(byte)', + i16 => '(short)', + u16 => '(short)', + string => '"' +); + +my %typeSuffix = ( + u64 => 'L', + i64 => 'L', + f32 => 'f', + string => '"' +); + +my %typeSignature = ( + 'byte' => 'B', + 'short' => 'S', + 'int' => 'I', + 'long' => 'J', + 'float' => 'F', + 'double' => 'D', + 'void' => 'V', + 'MemorySegment' => 'Ljdk/incubator/foreign/MemorySegment;', + 'MemoryAddress' => 'Ljdk/incubator/foreign/MemoryAddress;', +); + +# creates per-field type info, used by methods and structs +# the names are bit naff here +sub scanFields { + my $api = shift; + my $s = shift; + my $data = $api->{data}; + + my @members = (); + foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) { + my $info = $api->findType($m); + my $type = $info->{type}; + my $match = $info->{match}; + my $typeSizes = $code::typeSizes; + + foreach my $k (keys %{$type->{items}}) { + my $f = $type->{items}->{$k}; + + if (defined $f) { + my $e = eval $f; + if (!defined $e) { + print "$typeSizes{$match->{ctype}}\n"; + + print "$s->{name} field=$m->{name} deref=$m->{deref} regex=$type->{regex} $k=\n$f\n"; + print "match=".Dumper($match); + print "error: $! $@\n"; + die; + } else { + #print "$m->{name} $type->{regex} $k = $f = $e\n"; + } + $match->{$k} = $e; + } + } + + # hmm this should probably be in loop above, but there's clashes with things like {type} + foreach my $o (grep { defined $m->{$_} } 'tonative') { + $match->{$o} = findCode($api, $m->{$o}); + } + + $match->{name} = $m->{name}; + $match->{rename} = $m->{rename}; + $match->{segment} = 'segment()'; + $match->{scope} = 'scope()'; + + push @members, { field=>$m, type=>$type, match=>$match }; + } + @members; +} + +sub findCode { + my $api = shift; + my $v = shift; + + if ($v =~ m/^(code:.+)=(.*)$/) { + my $t = $api->{index}->{$1}; + my $l = genapi::findItem($t, $2); + + die "Uknown template '$1.$2'" if !defined($t) || !defined($l); + $l->{literal}; + } else { + $v; + } +} + +sub formatFunctionDescriptor { + my $api = shift; + my $members = shift; + my $d; + + if ($members->[0]->{field}->{deref} eq 'void') { + shift @$members; + $d = "FunctionDescriptor.ofVoid("; + } else { + $d = "FunctionDescriptor.of("; + } + + $d .= join(', ', map { formatTemplate($_->{match}->{layout}, $_->{match}) } @$members); + $d .= ')'; + + return $d; +} + +sub formatFunctionSignature { + my $api = shift; + my $members = shift; + my $result = shift @$members; + + return '('.join('', map { $typeSignature{$_->{match}->{carrier}} } @$members).')' + .$typeSignature{$result->{match}->{carrier}}; +} + +sub formatStructLayout { + my $api = shift; + my $s = shift; + my $members = shift; + my $count = 0; + my $lastOffset = 0; + my $maxSize = 8; + my $layout; + + $layout = "\tpublic static final GroupLayout LAYOUT = MemoryLayout.$s->{type}Layout(\n\t\t"; + + foreach my $i (@$members) { + my $m = $i->{field}; + my $type = $i->{type}; + my $match = $i->{match}; + + $maxSize = bitfieldSize($m) if (bitfieldSize($m) > $maxSize); + + if ($match->{layout}) { + if ($m->{offset} > $lastOffset) { + $layout .= ",\n\t\t" if $count++; + $layout .= 'MemoryLayout.paddingLayout('.($m->{offset} - $lastOffset).')'; + } + $layout .= ",\n\t\t" if $count++; + $layout .= formatTemplate($match->{layout}, $match).".withName(\"$m->{name}\")"; + $lastOffset = $m->{offset} + $m->{size}; + } + } + if ($s->{size} > $lastOffset) { + $layout .= ",\n\t\t" if ($count++ > 0); + $layout .= 'MemoryLayout.paddingLayout('.($s->{size} - $lastOffset).')'; + } + + $layout .= "\n\t).withBitAlignment($maxSize);\n"; + $layout; +} + +sub formatDefine { + my $api = shift; + my $s = shift; + + my $d = join "\n\t", map { + my $type = $_->{type}; + my $name = $_->{name}; + my $value = $_->{value}; + + "public static final $defineType{$type} $name = $typePrefix{$type}$value$typeSuffix{$type};"; + } @{$s->{items}}; + + return "\t$d\n"; +} + +sub formatEnum { + my $api = shift; + my $s = shift; + my $type = $defineType{$s->{value_type}}; + + my $d = join "\n\t", "// enum $s->{name}", map { + my $name = $_->{name}; + my $value = $_->{value}; + + "public static final $type $name = $typePrefix{$type}$value$typeSuffix{$type};"; + } @{$s->{items}}; + + return "\t$d\n"; +} + +sub formatTemplate { + my $template = shift; + my $vars = shift; + my $prefix = shift; + my $result; + + #print "apply-template vars=".Dumper($vars); + + while ($template =~ m/^(.*?)\{([\w-]+)\}(.*)$/s) { + $result .= $1; + + #print " $2 -> $vars->{$2}\n"; + + if (defined $vars->{$2}) { + $template = $vars->{$2}.$3; + } else { + $result .= "{$2}"; + $template = $3; + } + } + $result .= $template; + $result =~ s/^/$prefix/gm; + $result; +} + +# takes a template entry applies options and then formats it +# applyTemplate(template-item, vars) +sub applyTemplate { + my $code = shift; + my $match = shift; + my $vars = \%{$match}; + + foreach my $set (genapi::optionValuesAll('set', $code)) { + $vars->{$1} = $2 if ($set =~ m/^(\w+)=(.*)/); + } + + formatTemplate($code->{literal}, $vars); +} + +sub bitfieldSize { + my $m = shift; + my $size = $m->{size}; + + return 64 if ($size > 32); + return 32 if ($size > 16); + return 16 if ($size > 8); + return 8; +} + +sub bitfieldType { + my $m = shift; + my $size = $m->{size}; + return 'long' if ($size > 32); + return 'int' if ($size > 16); + return 'short' if ($size > 8); + return 'byte'; +} + +sub bitfieldIndex { + use integer; + my $m = shift; + + $m->{offset} / bitfieldSize($m); +} + +sub bitfieldOffset { + use integer; + my $m = shift; + + $m->{offset} & (bitfieldSize($m) - 1); +} + +sub bitfieldMask { + use integer; + my $m = shift; + + sprintf("0x%x", ((1 << $m->{size}) - 1) << bitfieldOffset($m)).$typeSuffix{bitfieldType($m)}; +} + +1; diff --git a/src/export-defines b/src/export-defines index b32d5e8..1774d21 100755 --- a/src/export-defines +++ b/src/export-defines @@ -19,6 +19,11 @@ my $hackformat; while (@ARGV) { my $cmd = shift; + if ($cmd =~ m/^(-[^-])(.+)/) { + $cmd = $1; + unshift @ARGV, $2; + } + if ($cmd eq "-t") { $package = shift; } elsif ($cmd eq "-d") { @@ -48,6 +53,9 @@ if ($hackformat == 1) { my $conf = new genconfig2({ include => \@includes }, $control); $defs = $conf->{objects}; @exports = grep { $_->{type} eq 'define' } @{$defs}; + foreach $export (@exports) { + $export->{options}->[0] = $conf->findInclude($export->{options}->[0]); + } } else { $defs = loadControlFile($control); @exports = @{$defs->{define}}; @@ -67,12 +75,7 @@ foreach $export (@exports) { foreach $inc (@{$export->{items}}) { my @options = @{$inc->{options}}; - if ($inc->{match} =~ m@^/(.*)/$@) { - print "$export->{name} - $inc->{match} - regex $1\n"; - $inc->{regex} = qr/$1/; - } else { - $inc->{regex} = qr/^$inc->{match}$/; - } + next if ($inc->{match} =~ m/^(define|enum):/on); $inc->{mode} = "include"; foreach $o (@{$inc->{options}}) { @@ -80,6 +83,16 @@ foreach $export (@exports) { $inc->{mode} = $o; } } + + if ($inc->{match} =~ m@^/(.*)/$@) { + print "$export->{name} - $inc->{match} - regex $1\n"; + $inc->{regex} = qr/$1/; + } elsif ($inc->{mode} =~ m/^file-/) { + $inc->{regex} = qr@/$inc->{match}$|$inc->{match}$@; + } else { + $inc->{regex} = qr/^$inc->{match}$/; + } + if ($inc->{mode} =~ m/include/) { $includes += 1; } else { @@ -92,13 +105,11 @@ foreach $export (@exports) { my @options = @{$export->{options}}; - foreach $o (@options) { - if ($o =~ m/header=(.*)/) { - $export->{header} = $1; - } elsif ($o =~ m/default=(.*)/) { + $export->{header} = $options[0]; + + foreach $o (@options[1..$#options]) { + if ($o =~ m/default=(.*)/) { $export->{default} = $1; - } elsif ($#options == 0) { - $export->{header} = $o; } else { print STDERR "unknown defines option '$o'\n"; } @@ -118,7 +129,7 @@ foreach $export (@exports) { my $header = $export->{header}; if (!defined($rawDefines{$header})) { - $rawDefines{$header} = scanDefines($header, { CPPFLAGS=>$cppflags, keep_comments=>0 }); + $rawDefines{$header} = scanDefines($header, { CPPFLAGS=>$CPPFLAGS, keep_comments=>0 }); } $export->{rawDefines} = $rawDefines{$header}; @@ -158,12 +169,21 @@ foreach $export (@exports) { } -open (my $fh, ">", "$output~") || die("can't open $output~ for writing"); - +# output the .c file +open (my $fh, ">", "$output-gen.c~") || die("can't open $output-gen,c~ for writing"); export($fh, \@exports); - close ($fh) || die("error writing"); -rename "$output~",$output || die("error overwriting $output"); +rename "$output-gen.c~","$output-gen.c" || die("error overwriting $output-gen.c"); + +# compile it +print "cc -o $output-gen $CPPFLAGS $output-gen.c\n"; +system("cc -o $output-gen $CPPFLAGS $output-gen.c") == 0 || die("error compiling $output-gen.c"); + +# execute it +print "$output-gen $output~\n"; +system("$output-gen $output~") == 0 || die("error executing $output-gen"); +print "mv $output~\ $output\n"; +rename "$output~","$output" || die("error overwriting $output"); exit 0; @@ -189,6 +209,7 @@ END # this is effectively a function overloading system so that the # compiler can select the datatype of the evaluation of the # definition. + # it should probably be c++ print $fp <{keep_comments} ? '-CC' : '')." $o->{CPPFLAGS} $header\n"; + + open (my $in,"-|","cpp -dD ".($o->{keep_comments} ? '-CC' : '')." $o->{CPPFLAGS} $header") // die("Can't find include file: $header"); while (<$in>) { # line markers if (m/^\# (\d+) \"([^\"]*)/) { @@ -279,7 +302,7 @@ sub scanDefines { } # accumulate comments # single line comments override multi-line - if ($o{keep_comments}) { + if ($o->{keep_comments}) { if (m@/\*(.*)\*/@) { do { $lastc = $1 if $lastc eq ""; @@ -364,7 +387,7 @@ sub loadControlFile { }; push @{$def{$1}}, $target; } elsif (/\S/) { - die("invalid line: %_"); + die("invalid line: '$_'"); } } diff --git a/src/export.cc b/src/export.cc index 611ab3f..ac87eb3 100644 --- a/src/export.cc +++ b/src/export.cc @@ -73,6 +73,8 @@ extern const char *tree_codes[]; int plugin_is_GPL_compatible; // must be defined for the plugin to run static FILE *output_file; +static char *output_path; // name +static char *output_tmp; // name~ static int output_enabled = 1; static int debug_level = 0; @@ -133,12 +135,24 @@ static void list_clear(struct list *list) { struct node *node; while ((node = list_remhead(list))) { - if (debug_level > 2) + if (debug_level > 0) fprintf(stderr, " free: %s\n", node->name); free(node); } } +static void todump_add(tree type, const char *name) { + struct node *node = node_alloc(type, name); + + list_add(&todump, node); +} + +static void todump_addhead(tree type, const char *name) { + struct node *node = node_alloc(type, name); + + list_addhead(&todump, node); +} + // returns 0 if type has no size (i.e VOID_TYPE) static bool is_struct_or_union(const_tree type) { return RECORD_TYPE == TREE_CODE(type) || UNION_TYPE == TREE_CODE(type); @@ -267,7 +281,7 @@ static void export_desc(tree field, tree field_type, struct buffer *b) { case RECORD_TYPE: case UNION_TYPE: if (TYPE_IDENTIFIER(field_type)) { - list_add(&todump, node_alloc(field_type, NULL)); + todump_add(field_type, NULL); buffer_add(b, "${"); buffer_add(b, value_name(field_type)); @@ -275,7 +289,7 @@ static void export_desc(tree field, tree field_type, struct buffer *b) { } else { char *name = stack_path(&context_stack, "_"); - list_add(&todump, node_alloc(field_type, name)); + todump_add(field_type, name); buffer_add(b, "${"); buffer_add(b, name); @@ -382,7 +396,7 @@ static void export_cdesc(tree field, tree field_type, struct buffer *b) { case RECORD_TYPE: case UNION_TYPE: if (TYPE_IDENTIFIER(field_type)) { - list_add(&todump, node_alloc(field_type, NULL)); + todump_add(field_type, NULL); buffer_add(b, "${"); buffer_add(b, value_name(field_type)); @@ -390,7 +404,7 @@ static void export_cdesc(tree field, tree field_type, struct buffer *b) { } else { char *name = stack_path(&context_stack, "_"); - list_add(&todump, node_alloc(field_type, name)); + todump_add(field_type, name); buffer_add(b, "${"); buffer_add(b, name); @@ -482,7 +496,7 @@ static void export_param(tree field, tree field_type, size_t field_size) { // note it is added to todump in reverse buffer_init(&b, 256); export_desc(field, field_type, &b); - list_addhead(&todump, node_alloc(field_type, b.data)); + todump_addhead(field_type, b.data); generate(" type => 'call:%s', ", b.data); if (debug_level > 0) { fprintf(stderr, "save for later param type %p root type %p '%s'\n", field_type, root_type, b.data); @@ -495,9 +509,9 @@ static void export_param(tree field, tree field_type, size_t field_size) { } } - buffer_init(&b, 256); - export_cdesc(field, field_type, &b); - free(b.data); + //buffer_init(&b, 256); + //export_cdesc(field, field_type, &b); + //free(b.data); break; } @@ -523,7 +537,7 @@ static void export_param(tree field, tree field_type, size_t field_size) { } else { char *name = stack_path(&context_stack, "_"); - list_add(&todump, node_alloc(field_type, name)); + todump_add(field_type, name); generate(" type => '%s:%s',", us, name); free(name); } @@ -548,7 +562,12 @@ static void export_params(tree func) { if (fwd) { // use the forward reference to find the names if (debug_level > 0) - fprintf(stderr, "found forward reference @ %p\n", fwd); + fprintf(stderr, "found forward reference @ %p\n", fwd->type); + + // so sometimes at this point the parameters stack has items we should + // remove ... but not always. apart from leaving garbage on the + // stack it doesn't seem to upset the output + name = fwd->list.head; } else { // paramter names are in the paramters list @@ -654,6 +673,21 @@ static void export_fields(tree first_field, size_t base_offset, int indent) { } } +// try to find a filename for this node +// it isn't really good enough for filtering, e.g. enums have no name +static const char *source_filename(tree type) { + if (DECL_P(type)) { + expanded_location xloc = expand_location(DECL_SOURCE_LOCATION(type)); + return xloc.file; + } else if (is_struct_or_union(type)) { + for (tree field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) { + return source_filename((field)); + } + } + // shrug + return ""; +} + /* Main entry point for exporting any type. */ @@ -662,9 +696,6 @@ static void export_type(tree type, const char *names) { tree deftype; tree target; - if (debug_level > 1) - fprintf(stderr, "export_type(%s, %s)\n", ZTREE_CODE(type), names); - switch (TREE_CODE(type)) { case TYPE_DECL: { if (!names) { @@ -673,15 +704,19 @@ static void export_type(tree type, const char *names) { return; names = IDENTIFIER_POINTER(name); } - if (hash_lookup(&dumped, names)) + if (hash_lookup(&dumped, names)) { + if (debug_level > 1) + fprintf(stderr, " - type already output, skipping"); return; + } hash_put(&dumped, node_alloc(type, names)); - if (debug_level > 1) - fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type)); - deftype = TREE_TYPE(type); target = target_type(deftype); + + if (debug_level > 1) + fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names); + switch (TREE_CODE(target)) { case FUNCTION_TYPE: { // function pointer typdef @@ -764,7 +799,7 @@ static void export_type(tree type, const char *names) { hash_put(&dumped, node_alloc(type, names)); if (debug_level > 1) - fprintf(stderr, "export type func decl %s\n", names); + fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names); generate("'func:%s' => { name => '%s', type => 'func',", names, names); @@ -809,7 +844,7 @@ static void export_type(tree type, const char *names) { hash_put(&dumped, node_alloc(type, names)); if (debug_level > 1) - fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type)); + fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names); generate("'call:%s' => { name => '%s', type => 'call',", names, names); generate(" ctype => '%s',", print_generic_expr_to_str(type)); @@ -859,7 +894,7 @@ static void export_type(tree type, const char *names) { hash_put(&dumped, node_alloc(type, names)); if (debug_level > 1) - fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type)); + fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names); generate("'%s:%s' => { name => '%s', type => '%s', size => %zu, fields => [\n", su, names, names, su, tree_to_uhwi(TYPE_SIZE(type))); @@ -931,9 +966,9 @@ static void export_type(tree type, const char *names) { target = target_type(deftype); if (debug_level > 1) { - fprintf(stderr, "field_decl type is '%s\n", ZTREE_CODE(deftype)); - fprintf(stderr, "type name is '%s'\n", TYPE_IDENTIFIER(deftype) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(deftype)) : ""); - fprintf(stderr, "target is '%s'\n", ZTREE_CODE(target)); + fprintf(stderr, "field: %-16s %s %s -> %s\n", ZTREE_CODE(deftype), names, + TYPE_IDENTIFIER(deftype) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(deftype)) : "", + ZTREE_CODE(target)); } // nb almost same as next case below, but we don't save the param info here @@ -980,7 +1015,7 @@ static void export_type(tree type, const char *names) { buffer_init(&b, 256); export_desc(deftype, target, &b); - list_addhead(&todump, node_alloc(target, b.data)); + todump_addhead(target, b.data); if (debug_level > 0) fprintf(stderr, "save for later param type %p '%s'\n", target, b.data); @@ -1050,7 +1085,7 @@ static void export_type(tree type, const char *names) { buffer_init(&b, 256); export_desc(deftype, target, &b); - list_addhead(&todump, node_alloc(target, b.data)); + todump_addhead(target, b.data); if (debug_level > 0) fprintf(stderr, "save for later param type %p '%s'\n", target, b.data); @@ -1061,7 +1096,7 @@ static void export_type(tree type, const char *names) { } if (debug_level > 0) - fprintf(stderr, "(push parameter '%s')\n", names); + fprintf(stderr, "(push parameter '%s' %s)\n", names, source_filename(type)); stack_push(¶meters, node_alloc(type, names)); break; } @@ -1082,6 +1117,8 @@ static void plugin_finish_type(void *event_data, void *user_data) { fprintf(stderr, "finish_type\n"); if (debug_level > 1) debug_tree(type); + if (debug_level > 2) + dump_node(type, TDF_ALL_VALUES, stderr); export_type(type, NULL); } @@ -1093,6 +1130,8 @@ static void plugin_finish_decl(void *event_data, void *user_data) { fprintf(stderr, "finish_decl %s\n", ZTREE_CODE(type)); if (debug_level > 1) debug_tree(type); + if (debug_level > 2) + dump_node(type, TDF_ALL_VALUES, stderr); export_type(type, NULL); } @@ -1116,11 +1155,16 @@ static void plugin_finish(void *event_data, void *user_data) { generate("# %s\n", n->name); generate("}\n"); - fclose(output_file); if (debug_level > 0) fprintf(stderr, "unhandled paramters:\n"); list_clear(¶meters); + + if (fclose(output_file) != 0 + || rename(output_tmp, output_path) != 0) { + perror(output_tmp); + exit(EXIT_FAILURE); + } } int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) { @@ -1139,7 +1183,11 @@ int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version exit(EXIT_FAILURE); } - output_file = fopen(output, "w"); + output_path = xstrdup(output); + output_tmp = (char *)xmalloc(strlen(output)+2); + strcpy(stpcpy(output_tmp, output), "~"); + + output_file = fopen(output_tmp, "w"); if (NULL == output_file) { perror(output); exit(EXIT_FAILURE); @@ -1147,6 +1195,7 @@ int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version generate("{\n"); + //register_callback(plugin_info->base_name, PLUGIN_INCLUDE_FILE, plugin_include_file, NULL); register_callback(plugin_info->base_name, PLUGIN_FINISH_DECL, plugin_finish_decl, NULL); register_callback(plugin_info->base_name, PLUGIN_FINISH_TYPE, plugin_finish_type, NULL); register_callback(plugin_info->base_name, PLUGIN_FINISH, plugin_finish, NULL); diff --git a/src/generate-api-2 b/src/generate-api-2 new file mode 100755 index 0000000..df765b2 --- /dev/null +++ b/src/generate-api-2 @@ -0,0 +1,390 @@ +#!/usr/bin/perl + +# usage +# -t package target package +# -d directory output root +# -v verbose +# -a datafile add datafile to the dataset, can be from export.so or export-defines, etc + +use Data::Dumper; + +use File::Path qw(make_path); +use File::Basename; + +use strict; + +use Carp 'verbose'; +$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; + +my $scriptPath = dirname(__FILE__); +push @INC,$scriptPath; + +require genapi; +require code; +require method; + +$Data::Dumper::Indent = 1; + +my $apidef = "api.api"; +my $vars = { + package => '', + output => 'bin', + verbose => 0, +}; +my @apilist; + +while (@ARGV) { + my $cmd = shift; + + if ($cmd =~ m/^(-[^-])(.+)/) { + $cmd = $1; + unshift @ARGV, $2; + } + + if ($cmd eq "-t") { + $vars->{package} = shift; + } elsif ($cmd eq "-d") { + $vars->{output} = shift; + } elsif ($cmd eq "-I") { + push @{$vars->{include}}, shift; + } elsif ($cmd eq "-v") { + $vars->{verbose}++; + } elsif ($cmd eq "-a") { + push @apilist, shift; + } else { + $apidef = $cmd; + } +} + +push @{$vars->{include}}, $scriptPath; + +print Dumper($vars) if $vars->{verbose}; + +my $api = new genapi($apidef, $vars, @apilist); + +#print Dumper($api); + +# TODO: make a module for this. +copySkeletonFile($api, 'Memory.java'); +copySkeletonFile($api, 'Frame.java'); + +exportLibraries($api); +exportStructs($api); +exportConstants($api); +exit 0; + +# copies a skeleton file and patches it to the target package +sub copySkeletonFile { + my $api = shift; + my $name = shift; + my $src = "$scriptPath/template/$name"; + my $dst = $api->{vars}->{package}; + + $dst =~ s@\.@/@g; + $dst = "$api->{vars}->{output}/$dst/$name"; + + make_path(dirname($dst)); + + open (my $d, ">", $dst.'~') || die ("Cannot open '$src' for writing"); + open (my $s, "<", $src) || die ("Cannot open '$dst' for reading"); + + while (<$s>) { + s/^package .*;/package $api->{vars}->{package};/o; + print $d $_; + } + + close $s; + close $d; + + rename ($dst.'~', $dst) || die ("unable to rename $d: $!"); +} + +sub formatFunction { + my $api = shift; + my $c = shift; + my $template = $api->{index}->{'code:method'}; + my $invoke = genapi::findItem($template, 'invoke'); + my $info = new method($api, $c); + + #print 'function='.Dumper($c); + #print 'members='.Dumper(\@members); + #print 'info='.Dumper($info); + + my $code; + + foreach my $l (split /\n/,Dumper($info->{vars}).Dumper($info->{arguments})) { + $code .= "// $l\n"; + } + + $code .= code::applyTemplate($invoke, $info->{vars}); + $code =~ s/^\s*\n//osgm; + + return $code; +} + +sub formatCall { + my $api = shift; + my $c = shift; + my $template = $api->{index}->{'code:method'}; + my $upcall = genapi::findItem($template, 'upcall'); + my $downcall = genapi::findItem($template, 'downcall'); + my $info = new method($api, $c); + + #print 'function='.Dumper($c); + #print 'members='.Dumper(\@members); + + my $code; + foreach my $l (split /\n/,Dumper($info)) { + $code .= "//$l\n"; + } + + $info->{vars}->{downcall} = ($c->{access} =~ m/r/) ? code::applyTemplate($downcall, $info->{vars}) : ''; + $info->{vars}->{upcall} = ($c->{access} =~ m/w/) ? code::applyTemplate($upcall, $info->{vars}) : ''; + + return $code.code::applyTemplate(genapi::findItem($api->{index}->{'code:class'}, 'call'), $info->{vars}); +} + +sub formatItems { + my $api = shift; + my $inc = shift; + my $res = shift; + my @list; + + @list = grep { + $api->{output}->{"$_->{type}:$_->{name}"}++; + $res->{seen}->{"$_->{type}:$_->{name}"}++ == 0 + } $api->findMatches($inc); + + if ($inc->{type} eq 'func') { + push @{$res->{func}}, map { formatFunction($api, $_) } @list; + } elsif ($inc->{type} eq 'define') { + push @{$res->{define}}, map { code::formatDefine($api, $_) } @list; + } elsif ($inc->{type} eq 'enum') { + push @{$res->{enum}}, map { code::formatEnum($api, $_) } @list; + } elsif ($inc->{type} eq 'call') { + push @{$res->{enum}}, map { formatCall($api, $_) } @list; + } else { + die; + } +} + +sub formatLibrary { + my $api = shift; + my $obj = shift; + my $res = shift; + my $data = $api->{data}; + my $d; + + print "library $obj->{name}\n"; + + foreach my $inc (@{$obj->{items}}) { + if ($inc->{type} eq 'library') { + $api->{output}->{"$inc->{match}"}++; + formatLibrary($api, $api->{index}->{$inc->{match}}, $res); + } elsif ($inc->{type} =~ m/func|call|define|enum/) { + print " $inc->{match}\n"; + formatItems($api, $inc, $res); + } elsif ($inc->{type} ne 'field') { + die; + } + } +} + +sub findTemplate { + my $api = shift; + my $obj = shift; + my $s = shift; + my $data = $api->{data}; + my $def = $api->{index}->{"$s->{type}:"}; + my $tmp = genapi::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def); + + if ($tmp =~ m/^(.+)=(.+)$/) { + return genapi::findItem($api->{index}->{$1}, $2); + } + die; + +} + +# TODO: embedded structs +sub formatStruct { + my $api = shift; + my $obj = shift; + my $s = shift; + my $data = $api->{data}; + my $seen = {}; + my $structTemplate = findTemplate($api, $obj, $s); + + my @members = code::scanFields($api, $s); + my @membersOutput = grep { $_->{field}->{output} } @members; + + my $varhandles = join '', map { code::formatTemplate($_->{match}->{varhandle}, $_->{match}, "\t") } @membersOutput; + my $accessors = join "\n", map { + my ($m, $type, $match) = ($_->{field}, $_->{type}, $_->{match}); + map { + my $accessor = $api->{index}->{$_}; + + map { code::applyTemplate($_, $match) } grep { $_ } map { genapi::findItem($accessor, $_) } map { + my @list = (); + if ($_ =~ m/r/o) { + push @list, 'get'; + push @list, 'geti' if $_ =~ m/i/o; + } + if ($_ =~ m/w/o) { + push @list, 'set'; + push @list, 'seti' if $_ =~ m/i/o; + } + @list; + } $m->{access}; + } split(/,/,genapi::optionValue('template', undef, $type)); + } @membersOutput; + + my $res = { + library => [], + func => [], + define => [], + enum => [], + call => [], + seen => {} + }; + + formatLibrary($api, $obj, $res); + + my $vars = { + %{$api->{vars}}, + layout => code::formatStructLayout($api, $s, \@members), + rename => $s->{rename}, + name => $s->{name}, + varhandles => $varhandles, + accessors => $accessors, + methods => join("\n", @{$res->{func}}), + defines => join("\n", @{$res->{define}}), + enums => join("\n", @{$res->{enum}}), + }; + + my $code; + foreach my $l (split /\n/,Dumper($s, \@members)) { + $code .= "//$l\n"; + } + + $api->{output}->{"$s->{type}:$s->{name}"} = 1; + + return $code.code::applyTemplate($structTemplate, $vars); +} + +# output all libraries +sub exportLibraries { + my $api = shift; + my $data = $api->{data}; + my $template = $api->{index}->{'code:class'}; + my $library = genapi::findItem($template, 'library'); + + foreach my $obj (grep { $_->{type} eq 'library' } @{$api->{api}}) { + next if $api->{output}->{"$obj->{type}:$obj->{name}"}; + next if $obj->{output} != 1; + + my $res = { + library => [], + func => [], + define => [], + enum => [], + call => [], + seen => {} + }; + + formatLibrary($api, $obj, $res); + + my $vars = { + %{$api->{vars}}, + name => $obj->{name}, + defines => join("\n", @{$res->{define}}), + enums => join("\n", @{$res->{enum}}), + funcs => join("\n", @{$res->{func}}), + calls => join("\n", @{$res->{call}}), + }; + + export($api, $obj->{name}, code::applyTemplate($library, $vars)); + } +} + +sub export { + my $api = shift; + my $name = shift; + my $text = shift; + + my $f = $api->openOutput($name); + print $f $text; + $api->closeOutput($name, $f); +} + +sub formatClass { + my $api = shift; + my $obj = shift; + my $s = shift; + + if ($s->{type} =~ m/struct|union/) { + return formatStruct($api, $obj, $s); + } elsif ($s->{type} eq 'call') { + return formatCall($api, $s); + } else { + die; + } +} + +sub exportStructs { + my $api = shift; + my $data = $api->{data}; + + # first those directly referenced + foreach my $obj (grep { $_->{type} =~ m/call|struct|union/ } @{$api->{api}}) { + my @list = $api->findMatches($obj); + + print "gen ".($#list+1)." $obj->{type} $obj->{name}\n"; + foreach my $s (@list) { + next if $api->{output}->{"$s->{type}:$s->{name}"}; + print " $s->{name}\n"; + + export($api, $s->{rename}, formatClass($api, $obj, $s)); + } + } + + # then anything else left using the default outputs + foreach my $s (grep { $_->{output} && ($_->{type} =~ m/struct|union|call/) && !$api->{output}->{"$_->{type}:$_->{name}"} } @{$api->{api}}) { + my $obj = $data->{"$s->{type}:"}; + export($api, $s->{rename}, formatClass($api, $obj, $s)); + } +} + +# exports any define/enum not yet included elsehwere +# TODO: this is sort of not very useful +sub exportConstants { + my $api = shift; + my $data = $api->{data}; + my $template = $api->{index}->{'code:class'}; + my $constant = genapi::findItem($template, 'constants'); + + # hmm, not sure if i should use obj or just the values directly here + foreach my $s (grep { $_->{type} =~ m/define|enum/ } values %{$api->{data}}) { + next if $api->{output}->{"$s->{type}:$s->{name}"}; + next if !$s->{output}; + + my $defines = ''; + my $enums = ''; + + $defines = code::formatDefine($api, $s) if $s->{type} eq 'define'; + $enums = code::formatEnum($api, $s) if $s->{type} eq 'enum'; + + my $vars = { + %{$api->{vars}}, + name => $s->{name}, + defines => $defines, + enums => $enums, + }; + + my $code; + foreach my $l (split /\n/,Dumper($s)) { + $code .= "//$l\n"; + } + + export($api, $s->{name}, $code.code::applyTemplate($constant, $vars)); + } +} diff --git a/src/method.pm b/src/method.pm new file mode 100644 index 0000000..490a743 --- /dev/null +++ b/src/method.pm @@ -0,0 +1,156 @@ + +package method; + +use strict; + +require code; + +sub fieldScope { + my $m = shift; + + if ($m->{scope} =~ m/^explicit/) { + 'scope$'; + } elsif ($m->{scope} eq 'instance') { + 'scope()'; + } else { + 'ResourceScope.globalScope()'; + } +} +sub fieldScopeAction { + my $m = shift; + + if ($m->{field}->{scope} =~ m/^explicit,(.*)$/) { + return code::formatTemplate("scope\$.addCloseAction(() -> $1", { %{$m->{match}}, value => 'res$' }); + } elsif ($m->{field}->{scope} =~ m/^explicit$/) { + 'scope$.addCloseAction(() -> res$.close());'; + } else { + (); + } +} +sub apply { + my $t = shift; + my $m = shift; + return code::formatTemplate($t, $m->{match}); +} + +sub new { + my $class = shift; + my $api = shift; + my $c = shift; + my @members = code::scanFields($api, $c); + my $result = shift @members; + + my $self = { + result => $result, + arguments => \@members, + vars => \%{$api->{vars}}, + method => $c, + }; + my $info = $self->{vars}; + + $info->{name} = $c->{name}; + $info->{rename} = $c->{rename}; + + my @list = map { apply('{type} {name}', $_) } grep { $_->{field}->{output} } @members; + push @list, 'ResourceScope scope$' if ($c->{scope} =~ m/explicit/); + $info->{'java-arguments'} = join ', ', @list; + $info->{'native-arguments'} = join ', ', map { apply('{carrier} {name}', $_) } @members; + + # for native downcalls + $info->{'native-result-define'} = ($result->{match}->{type} ne 'void') ? apply('{carrier} result$;', $result) : ''; + $info->{'native-result-assign'} = ($result->{match}->{type} ne 'void') ? apply('result$ = ({carrier})', $result) : ''; + $info->{'native-call'} = join ', ', map { + my $m = $_->{field}; + + if ($m->{instance}) { + "(jdk.incubator.foreign.Addressable)address()"; + } else { + my $name = $_->{match}->{name}; + + if ($m->{output} == 0 && ($m->{deref} =~ m/^u64:/)) { + $name .= '$h'; + } elsif ($m->{output} == 0 && $m->{implied}) { + $name = $m->{implied}; + } elsif (defined($m->{'array-size-source'})) { + # or some function? + $name = "Memory.size($m->{'array-size-source'}->{name})"; + } + code::formatTemplate("{tonative}", { %{$_->{match}}, value=>$name }) + } + } @members; + + # for java upcalls + $info->{'java-call'} = join ', ', map { code::formatTemplate('{tojava}', { %{$_->{match}}, value=>'{name}' }) } grep { $_->{field}->{output} } @members; + + $info->{'java-signature'} = code::formatFunctionSignature($api, [$result, @members]); + $info->{'function-descriptor'} = code::formatFunctionDescriptor($api, [$result, @members]); + + # hidden output arguments + # TODO: what about in/out arguments? they just fall out by not specifying them as scoped? + my @output = grep { $_->{field}->{output} == 0 && ($_->{field}->{deref} =~ m/^u64:/) && $_->{field}->{instance} == 0} @members; + + $info->{'native-output-define'} = join "\n\t\t", map { apply('{carrieri} {name};', $_) } @output; + $info->{'native-output-init'} = join ";\n\t\t\t", map { apply('{type} {name}$h = {type}.createArray(1, frame$);', $_) } @output; + $info->{'native-output-copy'} = join ";\n\t\t\t", map { apply('{name} = {name}$h.get(0);', $_) } @output; + + # also required for some tonative types? + my $x = grep { $_->{match}->{type} eq 'String' } @members; + $info->{'create-frame'} = ($#output >= 0 || grep { $_->{match}->{type} eq 'String' } @members) ? '(Frame frame$ = Memory.createFrame()) ' : ''; + + # result code handling + if ($c->{success}) { + $info->{'result-code'} = $c->{success}->{name}; + + # success test + if ($c->{success}->{success} eq '!null') { + $info->{'result-test'} = "if ($c->{success}->{name} != MemoryAddress.NULL) "; + $info->{'result-throw'} = 'throw new NullPointerException();'; + } else { + $info->{'result-test'} = 'if ('.join('||', map { "$c->{success}->{name} == $_" } split(/,/,$c->{success}->{success})).') '; + $info->{'result-throw'} = 'throw new RuntimeException("error="+'.$c->{success}->{name}.');'; + } + } else { + $info->{'result-test'} = ''; + $info->{'result-throw'} = ''; + } + + # success actions + my @onsuccess = (); + + push @onsuccess, code::findCode($api, $c->{onsuccess}) if defined($c->{onsuccess}); + + if (defined($c->{return})) { + # nb: this is only used for output parameters + my $res = (grep { $_->{field} == $c->{return} } $result, @members)[0]; + + $info->{'java-result'} = $res->{match}->{typei}; + push @onsuccess, fieldScopeAction($res); + #push @onsuccess, code::formatTemplate('return {tojavai};', { %{$res->{match}}, value => $res->{field}->{name}, scope=>fieldScope($res->{field}) }); + + $info->{'java-result-assign'} = code::formatTemplate('{typei} res$ = {tojavai};', { %{$res->{match}}, value => $res->{field}->{name}, scope=>fieldScope($res->{field}) }); + $info->{'java-result-return'} = 'return res$;'; + $info->{'trampoline-result-define'} = 'error'; + $info->{'trampoline-result-return'} = 'error'; + } elsif ($result->{field}->{output} && $result->{match}->{type} ne 'void') { + $info->{'java-result'} = $result->{match}->{type}; + push @onsuccess, fieldScopeAction($result); + + $info->{'java-result-assign'} = code::formatTemplate('{type} res$ = {tojava};', { %{$result->{match}}, value => 'result$', scope=>fieldScope($result->{field}) }); + $info->{'java-result-return'} = 'return res$;'; + + $info->{'trampoline-result-define'} = apply('{type} res$ = ', $result); + $info->{'trampoline-result-return'} = code::formatTemplate('return {tonative};', { %{$result->{match}}, value => 'res$' }); + } else { + $info->{'java-result'} = 'void'; + $info->{'java-result-assign'} = ''; + $info->{'java-result-return'} = 'return;'; + $info->{'trampoline-result-define'} = ''; + $info->{'trampoline-result-return'} = ''; + } + $info->{'on-success'} = join("\n\t\t\t\t", @onsuccess); + + $info->{'static'} = $c->{static} ? 'static ' : ''; + + bless $self, $class; + $self; +} diff --git a/src/tokenise.pm b/src/tokenise.pm new file mode 100644 index 0000000..cddf789 --- /dev/null +++ b/src/tokenise.pm @@ -0,0 +1,135 @@ + +# +# simple tokeniser +# +# tokens are +# 'self' : '{' ';' '}' +# 'token':'\S+' +# 'literal': '{{.*}}\n' may span multiple lines but final }} +# must be at end of line + +package tokenise; + +use Data::Dumper; +use strict; + +sub new { + my $class = shift; + my $path = shift; + my $self = { + state => 0, + line => [], + colno => 0, + type => 'none', + files => [], + file => { path=>$path, lineno=>0, lines=>[] } + }; + + open (my $in,"<",$path) || die("unable to open '$path': $@"); + @{$self->{file}->{lines}} = <$in>; + close $in; + + bless $self, $class; + return $self; +} + +# include a file at the current point +sub include { + my $self = shift; + my $path = shift; + + print "include '$path'\n"; + + push @{$self->{files}}, $self->{file}; + + $self->{file} = { path=>$path, lineno=>0, lines=>[] }; + + open (my $in,"<",$path) || die("unable to open '$path': $@"); + @{$self->{file}->{lines}} = <$in>; + close $in; + + $self->{state} = 0; + # should it swallow the rest of {line}? +} + +sub next { + my $self = shift; + my $token; + + #print "next $self->{state} $#{$self->{lines}} $#{$self->{line}}\n"; + # on entry state is in [0, 3, 4] + while ($#{$self->{file}->{lines}} >= 0 || $#{$self->{line}} >= 0 || $#{$self->{files}} >= 0) { + if ($#{$self->{line}} < 0) { + if ($#{$self->{file}->{lines}} < 0) { + $self->{file} = pop @{$self->{files}}; + } + my $t = shift @{$self->{file}->{lines}}; + $t =~ s/(?:^\#.*|\s+\#.*)//; + push @{$self->{line}}, split //,$t; + $self->{file}->{lineno} += 1; + $self->{colno} = 0; + } + my $c = shift @{$self->{line}}; + + $self->{colno} += 1; + + #printf("$self->{state} '$c'=\$%02x\n", ord($c)); + + if ($self->{state} == 0) { + if (($c =~ m/\s/)) { + next; + } elsif ($c =~ m/[;\}]/) { + $self->{type} = $c; + #print "char: $c\n"; + return $c; + } elsif ($c eq '{') { + if ($#{$self->{line}} >= 0 && $self->{line}->[0] eq '{') { + shift @{$self->{line}}; + $self->{state} = 3; + } else { + $self->{type} = $c; + #print "char: $c\n"; + return $c; + } + } elsif ($c) { + $token .= $c; + $self->{state} = 1; + } + } elsif ($self->{state} == 1) { + if ($c eq ';') { + unshift @{$self->{line}}, $c; + $self->{state} = 2; + $self->{type} = 'token'; + #print "tok: $token\n"; + return $token; + } elsif ($c =~ m/\S/) { + $token .= $c; + } else { + $self->{state} = 0; + $self->{type} = 'token'; + #print "tok: $token\n"; + return $token; + } + } elsif ($self->{state} == 2) { + $self->{state} = 0; + #print "char: $c\n"; + return $c; + } elsif ($self->{state} == 3) { + if ($c eq '}' && $#{$self->{line}} == 1 && $self->{line}->[0] eq '}') { + shift @{$self->{line}}; + $self->{state} = 0; + $self->{type} = 'literal'; + #print "lit: $token\n"; + return $token; + } else { + $token .= $c; + } + } + } + + die "invalid state=$self->{state}" if $self->{state} != 0; + + return undef; +} + +1; diff --git a/src/types.api b/src/types.api new file mode 100644 index 0000000..094311a --- /dev/null +++ b/src/types.api @@ -0,0 +1,256 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# +# TODO: add a 'mode' to type matching +# this woudld allow per-field overrides, e.g. char * field use String or MemorySegment or ByteArray depending on option + +# special case for bitfields +# TODO: well this is bit of a mess, maybe should use like code:method? +type /bitfield/ copy= { + layout; + type {{ bitfieldType($m) }} + carrier {{ "{type}" }} + tonative {{ "({type})({value})" }} + tojava {{ "({type})({value})" }} + + getshiftl {{ bitfieldSize($m) - $m->{size} - bitfieldOffset($m) }} + getshiftr {{ bitfieldSize($m) - $m->{size} }} + getshiftop {{ ($m->{type} =~ m/^i/) ? ">>" : ">>>" }} + + setmask {{ bitfieldMask($m) }} + setoffset {{ bitfieldOffset($m) }} + + varindex {{ bitfieldIndex($m) }} + + getnative {{ + "({type})((({type}){name}\$VH.get({segment}) << {getshiftl}) {getshiftop} {getshiftr})" + }} + setnative {{ + "$m->{name}\$VH.set({segment}, ({type})((({type}){name}\$VH.get({segment}) & ~{setmask}) | ((({value}) << {setoffset}) & {setmask})))" + }} + varhandle {{ "final static VarHandle {name}\$VH = MemoryLayout.sequenceLayout(Memory.".uc(bitfieldType($m)).").varHandle(MemoryLayout.PathElement.sequenceElement({varindex}));\n" }} +} + +# void, only for return type +type /void/ { + type {{ 'void' }} + carrier {{ 'void' }} + getnative; + setnative; + tonative; + tojava; +} + +# 1d array of pointer to pointer +type /^\[(\d+)u64:u64.*\]$/ { +} + +# 1d array of pointer to primitive +type /^\[(?\d+)u64:(?[uif]\d+)\]$/ + copy= template=code:getbyvalue,code:getsetelement { + + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }} + layout {{ 'MemoryLayout.sequenceLayout({length}, Memory.POINTER)' }} + tojava {{ !$m->{array} ? 'PointerArray.createArray({value}, {length}, {scope})' : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})' }} + + getsegment {{ '(MemorySegment){name}$SH.invokeExact({segment})' }} + getnative {{ !$m->{array} ? 'PointerArray.create({getsegment})' : 'HandleArray.create({getsegment}, (a, s) -> {typei}.createArray(a, Long.MAX_VALUE, s))' }} + setnative; + varhandle {{ + 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n". + 'final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement());'."\n" + }} + + typei {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }} + getnativei {{ '{typei}.createArray((MemoryAddress){name}$EH.get({segment}, {index}), Long.MAX_VALUE, {scope})' }} + setnativei {{ '{name}$EH.set({segment}, {index}, (jdk.incubator.foreign.Addressable)Memory.address({value}))' }} + +} + +# 1d array of pointer to type (struct or union or function?) +type /^\[(?\d+)u64:\$\{(\w+)\}\]$/ { + type {{ "HandleArray<$data->{$m->{type}}->{rename}>" }} + layout {{ "MemoryLayout.sequenceLayout({length}, Memory.POINTER)" }} +} + +# 1d array of (struct or union or function?) +type /^\[(?\d+)\$\{(\w+)\}\]$/ { + type {{ "$data->{$m->{type}}->{rename}" }} + layout {{ "MemoryLayout.sequenceLayout({length}, {type}.LAYOUT)" }} +} + +# 1d array of primitive +type /^\[(?\d+)(?[uif]\d+)\]$/ + copy= template=code:getbyvalue,code:getsetelement { + layout {{ "MemoryLayout.sequenceLayout({length}, Memory.".uc($typeSizes{$match->{ctype}}).")" }} + type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + tojava {{ "{type}.createArray({value}, $match->{length}, {scope})" }} + tonative {{ "(jdk.incubator.foreign.Addressable)Memory.address({value}" }} + + getnative {{ "{type}.create((MemorySegment)$m->{name}\$SH.invokeExact({segment}))" }} + setnative; + varhandle {{ + "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n". + "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement());\n" + }} + + typei {{ "$typeSizes{$match->{ctype}}" }} + getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index})" }} + setnativei {{ "$m->{name}\$EH.set({segment}, {index}, ({typei}){value})" }} +} + +# 2d array of primitive +type /^\[(?\d+)\[(?\d+)(?[uif]\d+)\]\]$/ + copy= template=code:getbyvalue,code:getsetelement2d { + layout {{ "MemoryLayout.sequenceLayout({length0}, MemoryLayout.sequenceLayout({length1}, Memory.".uc($typeSizes{$match->{ctype}})."))" }} + type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + tojava {{ "{type}.create({value})" }} + tonative {{ "(jdk.incubator.foreign.Addressable)Memory.address({value})" }} + + getnative {{ "{type}.create((MemorySegment)$m->{name}\$SH.invokeExact({segment}))" }} + setnative; + + typei {{ "$typeSizes{$match->{ctype}}" }} + getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index0}, {index1}))" }} + setnativei {{ "$m->{name}\$EH.set({segment}, {index0}, {index1}, ({typei}){value}))" }} + + varhandle {{ + "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n". + "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement(), MemotyLayout.PathElement.sequenceElement());\n" + }} +} + +# function pointer +type /^u64:\(/ copy= { + type {{ "FunctionPointer<$data->{$m->{type}}->{rename}>" }} + tojava {{ "$data->{$m->{type}}->{rename}.downcall({value}, {scope})" }} +} + +# **primitive +type /^u64:u64:(?[uif]\d+)$/ copy= { + length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }} + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }} + tojava {{ + !$m->{array} + ? 'PointerArray.createArray({value}, {length}, {scope})' + : 'HandleArray.createArray({value}, {length}, (a, s) -> {typei}.createArray(a, {length}, s), {scope})' + }} + typei {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} +} + +# **type +type /^u64:u64:\$\{(\w+)\}$/ copy= { + length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }} + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }} + tojava {{ + !$m->{array} + ? 'PointerArray.createArray({value}, {length}, {scope})' + : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})' + }} + # tojavai ... ? + carrieri {{ "MemoryAddress" }} + typei {{ "$data->{$m->{type}}->{rename}" }} + tojavai {{ "{typei}.create({value}, {scope})" }} +} + +# **void +type /^u64:u64:v$/ copy= { + typei {{ 'PointerArray' }} + type {{ "HandleArray<{typei}>" }} + tojava {{ "HandleArray.createArray({value}, Long.MAX_VALUE, PointerArray"."::create, {scope})" }} +} + +# *primitive, or string for [iNNu8] + +# idea for when i implement 'flag' matching for types +# *i8 with 'array' flag +# TODO: length, multiple flags? +type /^u64:(?i8)$/ select=array copy= { + type {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }} + tojava {{ '{type}.createArray({value}, Long.MAX_VALUE, {scope})' }} + carrieri {{ "{typei}" }} + typei {{ $typeSizes{$match->{ctype}} }} + tojavai {{ "({typei}){value}" }} +} + +# *i8 with 'segment' flag, length must be supplied (somewhere??) +type /^u64:(?i8)$/ select=segment copy= { + type {{ 'MemorySegment' }} + tojava {{ '{value}' }} + carrieri {{ "{typei}" }} + typei {{ 'byte' }} + tojavai {{ "({typei}){value}" }} +} + +# *i8 with no flag = String +type /^u64:(?i8)$/ copy= { + type {{ 'String' }} + tonative {{ '(jdk.incubator.foreign.Addressable)frame$.copy({value})' }} + + setnative {{ '{name}$VH.set({segment}, {copynative})' }} + copynative {{ 'SegmentAllocator.nativeAllocator(segment.scope()).allocateUtf8String({value})' }} + + tojava {{ '({value}).getUtf8String(0L)' }} +} + +# *Primitive fallback +type /^u64:(?[uif]\d+)$/ copy= { + type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + tonative {{ '(jdk.incubator.foreign.Addressable)Memory.address({value})' }} + tojava {{ ucfirst($typeSizes{$match->{ctype}})."Array.createArray({value}, Long.MAX_VALUE, {scope})" }} + carrieri {{ "{typei}" }} + typei {{ $typeSizes{$match->{ctype}} }} + tojavai {{ "({typei}){value}" }} +} + +# *type struct? handle? +type /^u64:\$\{(\w+)\}$/ copy= { + type {{ "$data->{$m->{type}}->{rename}" }} + tojava {{ "$data->{$m->{type}}->{rename}.create({value}, {scope})" }} +} + +# *void with size +type /^u64:v$/ select=array-size copy= { + type {{ 'MemorySegment' }} + tojava {{ "MemorySegment.ofAddress({value}, $m->{'array-size'}->{name}, {scope})" }} +} + +# *void +type /^u64:v$/ copy= { +} + +# primitive +type /^(?[uif]\d+)$/ copy= { + layout {{ "Memory.".uc($typeSizes{$match->{ctype}}) }} + carrier {{ "$typeSizes{$match->{ctype}}" }} + type {{ "$typeSizes{$match->{ctype}}" }} + tonative {{ '({type})({value})' }} + tojava {{ '({type})({value})' }} +} + +# type inline +type /^\$\{(\w+)\}$/ byvalue template=code:getbyvalue { + layout {{ '{type}.LAYOUT' }} + carrier {{ 'MemoryAddress' }} + type {{ "$data->{$m->{type}}->{rename}" }} + tojava {{ '{type}.create({value}, {scope})' }} + tonative {{ '(jdk.incubator.foreign.Addressable)Memory.address({value})' }} + getnative {{ '{type}.create(({carrier}){name}$SH.invokeExact({segment}), {scope})' }} + setnative; + varhandle {{ 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n" }} +} + +type template=code:getset { + getnative {{ '({carrier}){name}$VH.get({segment})' }} + setnative {{ '{name}$VH.set({segment}, {tonative})' }} + varhandle {{ 'final static VarHandle {name}$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n" }} + tojava {{ '{value}' }} + tonative {{ '{value}' }} +} + +type copy= { + layout {{ 'Memory.POINTER' }} + carrier {{ 'MemoryAddress' }} + type {{ 'MemoryAddress' }} + tonative {{ '(jdk.incubator.foreign.Addressable)Memory.address({value})' }} +} diff --git a/test-api-object/Makefile b/test-api-object/Makefile new file mode 100644 index 0000000..e18fc77 --- /dev/null +++ b/test-api-object/Makefile @@ -0,0 +1,44 @@ + +JAVA_HOME?=/opt/jdk-foreign/jvm/openjdk-19-internal +JAVAC=$(JAVA_HOME)/bin/javac +JAVA=$(JAVA_HOME)/bin/java + +JAVACFLAGS=--add-modules jdk.incubator.foreign + +api_SOURCES := $(wildcard ../src/api/*.java) +api_demo_SOURCES := $(wildcard src/api/test/*.java) +apigen_DEPS := $(api_SOURCES) $(wildcard ../src/*.api) $(wildcard ../src/*.pm ../src/generate-api-2) + +all:: + mkdir -p bin + +all:: bin/demo.built + +bin/api.built: bin/api-object.gen + $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes \ + $(shell find bin/java -name '*.java') + touch $@ + +bin/api-object.gen: bin/api.pm bin/api-defines.pm $(apigen_DEPS) + ../src/generate-api-2 -v -d bin/java -t proto.apiobject -a ./bin/api.pm -a ./bin/api-defines.pm api-object.api + touch $@ + +bin/api-defines.pm: ../api/api.h ../src/export-defines api-object.api + ../src/export-defines --hack-new-format-2 -v -I.. -d $@ api-object.api + +bin/api.pm: ../api/api.h ../src/export.so + gcc -fplugin=../src/export.so -fplugin-arg-export-output=$@ ./$< -o /dev/null + +bin/demo.built: $(api_demo_SOURCES) bin/api.built + $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes $(api_demo_SOURCES) + touch $@ + +demo: bin/demo.built + $(JAVA) --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \ + -Djava.library.path=../api/bin -cp bin/classes \ + api.test.TestAPI + +clean: + rm -rf bin + +.PHONY: demo clean all diff --git a/test-api/README b/test-api-object/README similarity index 100% rename from test-api/README rename to test-api-object/README diff --git a/test-api-object/api-object.api b/test-api-object/api-object.api new file mode 100644 index 0000000..91d14b8 --- /dev/null +++ b/test-api-object/api-object.api @@ -0,0 +1,46 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# maps functions to objects + +include types.api +include code.api + +struct default=all field:rename=studly-caps access=rw { +} +call default=all call:rename=call access=rw { +} + +struct api { + enum://; + define:API; + library:api-static; + library:api-calls; +} + +struct data { + func:print_data instance:0 rename=print; +} + +library api-static func:rename=s/^api_//,camel-case { + api_create success:result$=!null scope:result$=global; + api_func; + + # different forms of constructor + api_new_a scope:result$=global; + api_new_b scope:result$=global success:rc=0; + api_new_c scope:apip=global success:result$=0 return:apip; + api_new_d scope:apip=global success:api=!null return:apip; + api_new_e scope:apip=global success:rc=0 return:apip; +} + +# instance methods using library-level options +library api-calls func:rename=s/^api_//,camel-case instance:0 { + api_free; + api_data array-size:data=size; + api_void rename=avoid; +} + +# grab all defines that come from api.h +define API api/api.h { + api.h file-include; +} diff --git a/test-api-object/src/api/test/TestAPI.java b/test-api-object/src/api/test/TestAPI.java new file mode 100644 index 0000000..cc92c00 --- /dev/null +++ b/test-api-object/src/api/test/TestAPI.java @@ -0,0 +1,68 @@ + +package api.test; + +import jdk.incubator.foreign.*; + +import proto.apiobject.*; +import java.lang.invoke.*; + +public class TestAPI { + + public static void main(String[] args) { + System.loadLibrary("api"); + + try (Frame frame = Memory.createFrame(); + ResourceScope scope = ResourceScope.newConfinedScope()) { + + data a = data.create(frame); + data b = data.create(frame); + + Memory.FunctionPointer cb = Call__i32.upcall(() -> { + return 56; + }, scope); + + + a.setNext(b); + a.setA(1); + a.setB(2); + a.setC((byte)3); + a.setD((byte)4); + + + b.setA(5); + b.setB(6); + b.setC((byte)255); + b.setD((byte)255); + + b.setTestA(cb); + + System.out.println("from a"); + a.print(); + System.out.println("from b"); + b.print(); + + //api api = proto.api.api.create(frame); + //api.setFunca(api_func(Memory.ByteArray.create("funca", frame))); + //api.setFuncb(api_func(Memory.ByteArray.create("funcb", frame))); + //api.setFuncc(api_func(Memory.ByteArray.create("funcc", frame))); + + // dynamic lookup + System.out.println("call funca via symbol lookup"); + Memory.FunctionPointer funca = Call_i32_v.downcall(api.func("funca"), scope); + System.out.printf(" %s\n", funca.symbol()); + funca.function().call(12); + + api api = proto.apiobject.api.create(); + + System.out.println("call funca via function table"); + api.getFunca().function().call(99); + + api.setFunca(Call_i32_v.upcall( + i-> System.out.printf("java.funca: %d\n", i), + scope)); + + System.out.println("call funca via java upcall"); + api.getFunca().function().call(22); + } + } +} diff --git a/test-api-static/Makefile b/test-api-static/Makefile new file mode 100644 index 0000000..05df38a --- /dev/null +++ b/test-api-static/Makefile @@ -0,0 +1,46 @@ + +CFLAGS=-g -fPIC + +JAVA_HOME?=/opt/jdk-foreign/jvm/openjdk-19-internal +JAVAC=$(JAVA_HOME)/bin/javac +JAVA=$(JAVA_HOME)/bin/java + +JAVACFLAGS=--add-modules jdk.incubator.foreign + +api_SOURCES := $(wildcard ../src/api/*.java) +api_demo_SOURCES := $(wildcard src/api/test/*.java) +apigen_DEPS := $(api_SOURCES) $(wildcard ../src/*.api) $(wildcard ../src/*.pm ../src/generate-api-2) + +all:: + mkdir -p bin + +all:: bin/demo.built + +bin/api.built: bin/api-static.gen + $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes \ + $(shell find bin/java -name '*.java') + touch $@ + +bin/api-static.gen: bin/api.pm bin/api-defines.pm api-static.api $(apigen_DEPS) + ../src/generate-api-2 -v -d bin/java -t proto.apistatic -a ./bin/api.pm -a ./bin/api-defines.pm api-static.api + touch $@ + +bin/api-defines.pm: ../api/api.h ../src/export-defines api-static.api + ../src/export-defines --hack-new-format-2 -I.. -d $@ api-static.api + +bin/api.pm: ../api/api.h ../src/export.so + gcc -fplugin=../src/export.so -I.. -fplugin-arg-export-output=$@ $< -o /dev/null + +bin/demo.built: $(api_demo_SOURCES) bin/api.built + $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes $(api_demo_SOURCES) + touch $@ + +demo: bin/demo.built + $(JAVA) --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \ + -Djava.library.path=../api/bin -cp bin/classes \ + api.test.TestAPI + +clean: + rm -rf bin + +.PHONY: demo clean all diff --git a/test-api-static/README b/test-api-static/README new file mode 100644 index 0000000..59a3438 --- /dev/null +++ b/test-api-static/README @@ -0,0 +1,8 @@ + +Introduction +------------ + +Small generator and prototyping area for a c library. + +This is mostly to test out ideas like pattern matching for mostly +automatic api conversion. diff --git a/test-api-static/api-static.api b/test-api-static/api-static.api new file mode 100644 index 0000000..e3c7eca --- /dev/null +++ b/test-api-static/api-static.api @@ -0,0 +1,25 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# example of a simple 'static' library with all functions +# and constants in one class and no special handling + +include types.api +include code.api + +# collect all functions and constants into one object +library APILib load=api { + enum://; + define:API; + func://; +} + +struct default=all access=rw field:rename=studly-caps { +} + +call access=rw call:rename=call { +} + +# grab all defines in api.h +define API api/api.h { + api.h file-include; +} diff --git a/test-api/src/api/test/TestAPI.java b/test-api-static/src/api/test/TestAPI.java similarity index 92% rename from test-api/src/api/test/TestAPI.java rename to test-api-static/src/api/test/TestAPI.java index 108d898..559498f 100644 --- a/test-api/src/api/test/TestAPI.java +++ b/test-api-static/src/api/test/TestAPI.java @@ -3,8 +3,8 @@ package api.test; import jdk.incubator.foreign.*; -import proto.api.*; -import static proto.api.APILib.*; +import proto.apistatic.*; +import static proto.apistatic.APILib.*; import java.lang.invoke.*; public class TestAPI { @@ -26,8 +26,8 @@ public class TestAPI { a.setNext(b); a.setA(1); a.setB(2); - a.setC(3); //a.setC((byte)3); - a.setD(4); //a.setD((byte)4); + a.setC((byte)3); + a.setD((byte)4); b.setA(5); diff --git a/test-api/Makefile b/test-api/Makefile deleted file mode 100644 index a7a8666..0000000 --- a/test-api/Makefile +++ /dev/null @@ -1,55 +0,0 @@ - -CFLAGS=-g -fPIC - -JAVA_HOME?=/opt/jdk-foreign/jvm/openjdk-19-internal -JAVAC=$(JAVA_HOME)/bin/javac -JAVA=$(JAVA_HOME)/bin/java - -JAVACFLAGS=--add-modules jdk.incubator.foreign - -api_SOURCES := $(wildcard ../src/api/*.java) -api_demo_SOURCES := $(wildcard src/api/test/*.java) - -all:: - mkdir -p bin - -all:: bin/demo.built bin/libapi.so - -bin/api.built: bin/api.gen - $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes \ - $(shell find bin/java -name '*.java') - touch $@ - -bin/api.gen: bin/api.pm ../src/generate-native $(api_SOURCES) bin/api-defines.pm api.api - ../src/generate-native -v -d bin/java -t proto.api -a ./bin/api.pm -a ./bin/api-defines.pm api.api - touch $@ - -bin/api-defines.pm: api.h ../src/export-defines api.api - ../src/export-defines -d bin/api-defines.c api.api - $(CC) -o bin/api-defines -I. bin/api-defines.c - bin/api-defines $@~ - mv $@~ $@ - -bin/api.pm: api.h ../src/export.so - gcc -fplugin=../src/export.so -fplugin-arg-export-output=$@~ ./$< -o /dev/null - mv $@~ $@ - -bin/demo.built: $(api_demo_SOURCES) bin/api.built - $(JAVAC) $(JAVACFLAGS) -cp bin/classes -d bin/classes $(api_demo_SOURCES) - touch $@ - -bin/api.o: api.c api.h - $(CC) $(CFLAGS) -c -o $@ $< - -bin/libapi.so: bin/api.o - $(CC) -o $@ -shared $^ - -demo: bin/demo.built bin/libapi.so - $(JAVA) --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \ - -Djava.library.path=bin -cp bin/classes \ - api.test.TestAPI - -clean: - rm -rf bin - -.PHONY: demo clean all diff --git a/test-api/api.api b/test-api/api.api deleted file mode 100644 index cfe3103..0000000 --- a/test-api/api.api +++ /dev/null @@ -1,15 +0,0 @@ - -# collect all functions and the defines we want -library APILib load=api { - define:api-defines - func:// -} - -# grab all structures -struct // default=all field:rename=studly-caps { -} - -# grab all defines in api.h -define api-defines api.h { - api.h file-include -} diff --git a/test-api/api.h b/test-api/api.h deleted file mode 100644 index 4e6b023..0000000 --- a/test-api/api.h +++ /dev/null @@ -1,39 +0,0 @@ - -#define API_FOO "12" -#define API_MAKEVERSION(a, b) (a <<16) | b -#define API_VERSION API_MAKEVERSION(1, 0) -#define API_A 12UL -#define API_B 12L -#define API_C 12U -#define API_D 12.0 -#define API_E 12.0f - -struct data { - struct data *next; - - int a; - int b; - int c; // c:3; // bitfields not implemented with new generator yet - unsigned d; // d:5; - - int (*test_a)(void); - - char array[12]; -}; - -void print_data(struct data *data); - -struct api { - void (*funca)(int a); - int (*funcb)(int b); - int (*funcc)(float b); -}; - -void *api_func(const char *name); -struct api *api_create(void); - -struct list_node { - struct list_node *succ; - struct list_node *pred; - char name[64-16]; -}; -- 2.39.2