From f01b6cb0dbc8cec7ec5f31a03ffc0affb3a53b90 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Wed, 22 Dec 2021 11:45:59 +1030 Subject: [PATCH] Updated for openjdk-19-internal Wrote entirely new generator for c header files. Added vulkan demo. Generates api from api specification. Makefile tweaks. --- src/export.cc | 158 +- src/generate | 852 ------ src/generate-api | 1395 ---------- src/generate-native | 893 ++++++ src/template/Frame.java | 136 + src/template/Memory.java | 617 +++++ src/template/Native.java | 432 +++ test-api/Makefile | 42 +- test-api/api.c | 32 +- test-api/api.h | 12 +- test-api/api/test/TestAPI.java | 110 - test-api/src/api/test/TestAPI.java | 69 + test-vulkan/Makefile | 41 + test-vulkan/generate-vulkan | 2443 +++++++++++++++++ test-vulkan/mandelbrot.comp | 64 + test-vulkan/src/zvk/Frame.java | 136 + test-vulkan/src/zvk/Memory.java | 560 ++++ test-vulkan/src/zvk/PFN_Test.java | 107 + .../src/zvk/PFN_vkDebugReportCallbackEXT.java | 62 + .../PFN_vkDebugUtilsMessengerCallbackEXT.java | 49 + .../PFN_vkDeviceMemoryReportCallbackEXT.java | 48 + .../zvk/VkPhysicalDeviceGroupProperties.java | 80 + test-vulkan/src/zvk/test/TestVulkan.java | 534 ++++ test-vulkan/template/VkDevice-part.java | 62 + test-vulkan/template/VkInstance-part.java | 79 + 25 files changed, 6563 insertions(+), 2450 deletions(-) delete mode 100755 src/generate delete mode 100755 src/generate-api create mode 100755 src/generate-native create mode 100644 src/template/Frame.java create mode 100644 src/template/Memory.java create mode 100644 src/template/Native.java delete mode 100644 test-api/api/test/TestAPI.java create mode 100644 test-api/src/api/test/TestAPI.java create mode 100644 test-vulkan/Makefile create mode 100755 test-vulkan/generate-vulkan create mode 100644 test-vulkan/mandelbrot.comp create mode 100644 test-vulkan/src/zvk/Frame.java create mode 100644 test-vulkan/src/zvk/Memory.java create mode 100644 test-vulkan/src/zvk/PFN_Test.java create mode 100644 test-vulkan/src/zvk/PFN_vkDebugReportCallbackEXT.java create mode 100644 test-vulkan/src/zvk/PFN_vkDebugUtilsMessengerCallbackEXT.java create mode 100644 test-vulkan/src/zvk/PFN_vkDeviceMemoryReportCallbackEXT.java create mode 100644 test-vulkan/src/zvk/VkPhysicalDeviceGroupProperties.java create mode 100644 test-vulkan/src/zvk/test/TestVulkan.java create mode 100644 test-vulkan/template/VkDevice-part.java create mode 100644 test-vulkan/template/VkInstance-part.java diff --git a/src/export.cc b/src/export.cc index 17d4dad..4a34c58 100644 --- a/src/export.cc +++ b/src/export.cc @@ -32,6 +32,10 @@ https://blog.adacore.com/bindings-gcc-plugins */ +/* + TODO: get header name from tree + */ + /* function declarations, i think @@ -46,6 +50,7 @@ FUNCTION_DECL DECL_ARGUENTS TREE_TYPE(item):PARM_DECL DECL_NAME(item) #include #include +#include #include #include #include @@ -68,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 int output_enabled = 1; + static int debug_level = 0; static void debug_tree_helper(tree t, const char *msg) { @@ -89,6 +96,17 @@ static struct list parameters; // last list of params static struct hash forward_types; +static int generate(const char *fmt, ...) { + int res = 0; + if (output_enabled) { + va_list ap; + va_start(ap, fmt); + res = vfprintf(output_file, fmt, ap); + va_end(ap); + } + return res; +} + /* Join all names in the stack, in reverse order. */ @@ -127,8 +145,10 @@ static bool is_struct_or_union(const_tree type) { } static void print_spaces(int n) { - for (int i = 0; i < n; ++i) - fputc('\t', output_file); + if (output_enabled) { + for (int i = 0; i < n; ++i) + fputc('\t', output_file); + } } static int is_ref_type(tree type) { @@ -409,7 +429,7 @@ static void export_param(tree field, tree field_type, size_t field_size) { buffer_init(&b, 256); export_desc(field, field_type, &b); - fprintf(output_file, " deref => '%s',", b.data); + generate(" deref => '%s',", b.data); free(b.data); field_type = simple_type(field_type); @@ -419,17 +439,17 @@ static void export_param(tree field, tree field_type, size_t field_size) { break; } case VOID_TYPE: - fprintf(output_file, " type => 'void',"); - fprintf(output_file, " ctype => 'void',"); + generate(" type => 'void',"); + generate(" ctype => 'void',"); break; case ENUMERAL_TYPE: { #if defined(TYPED_ENUMS) const char *names = TYPE_IDENTIFIER(field_type) ? value_name(field_type) : "enum"; - fprintf(output_file, " type => 'enum:%s',", names); + generate(" type => 'enum:%s',", names); #else - fprintf(output_file, " type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size); + generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size); #endif - fprintf(output_file, " ctype => 'enum %s',", value_name(field_type)); + generate(" ctype => 'enum %s',", value_name(field_type)); break; } case FUNCTION_TYPE: { @@ -438,13 +458,14 @@ static void export_param(tree field, tree field_type, size_t field_size) { // If this is a typedef we might have a name for the type, otherwise it's a signature based name if (root_type && TYPE_IDENTIFIER(root_type)) { - fprintf(output_file, " type => 'call:%s', ", value_name(root_type)); + generate(" type => 'call:%s', ", value_name(root_type)); } else { - fprintf(stderr, "save for later param type %p\n", field_type); + if (debug_level > 0) + fprintf(stderr, "save for later param type %p\n", field_type); buffer_init(&b, 256); export_desc(field, field_type, &b); list_add(&todump, node_alloc(field_type, b.data)); - fprintf(output_file, " type => 'call:%s', ", b.data); + generate(" type => 'call:%s', ", b.data); } buffer_init(&b, 256); @@ -454,16 +475,16 @@ static void export_param(tree field, tree field_type, size_t field_size) { break; } case REAL_TYPE: - fprintf(output_file, " ctype => '%s',", value_name(field_type)); - fprintf(output_file, " type => 'f%zu',", field_size); + generate(" ctype => '%s',", value_name(field_type)); + generate(" type => 'f%zu',", field_size); break; case INTEGER_TYPE: if (TREE_CODE(field) == FIELD_DECL && DECL_BIT_FIELD(field)) { - fprintf(output_file, " ctype => 'bitfield',"); - fprintf(output_file, " type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', value_size(DECL_SIZE(field))); + generate(" ctype => 'bitfield',"); + generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', value_size(DECL_SIZE(field))); } else { - fprintf(output_file, " ctype => '%s',", value_name(field_type)); - fprintf(output_file, " type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size); + generate(" ctype => '%s',", value_name(field_type)); + generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size); } break; case RECORD_TYPE: @@ -471,12 +492,12 @@ static void export_param(tree field, tree field_type, size_t field_size) { const char *us = TREE_CODE(field_type) == RECORD_TYPE ? "struct" : "union"; if (TYPE_IDENTIFIER(field_type)) { - fprintf(output_file, " type => '%s:%s',", us, value_name(field_type)); + generate(" type => '%s:%s',", us, value_name(field_type)); } else { char *name = stack_path(&context_stack, "_"); list_add(&todump, node_alloc(field_type, name)); - fprintf(output_file, " type => '%s:%s',", us, name); + generate(" type => '%s:%s',", us, name); free(name); } break; @@ -499,7 +520,8 @@ static void export_params(tree func) { if (fwd) { // use the forward reference to find the names - fprintf(stderr, "found forward reference @ %p\n", fwd); + if (debug_level > 0) + fprintf(stderr, "found forward reference @ %p\n", fwd); name = fwd->list.head; } else { // paramter names are in the paramters list @@ -513,7 +535,8 @@ static void export_params(tree func) { struct node *decl = stack_pull(¶meters); if (decl) { - fprintf(stderr, "(pull parameter '%s')\n", decl->name); + if (debug_level > 0) + fprintf(stderr, "(pull parameter '%s')\n", decl->name); stack_push(&args, decl); } else fprintf(stderr, "ERROR: parameter %d missing parameter declaration\n", id); @@ -531,10 +554,10 @@ static void export_params(tree func) { if (!TREE_CHAIN(param) && TREE_CODE(param_type) == VOID_TYPE) break; - fprintf(output_file, "\t\t{"); + generate("\t\t{"); // size: do we need it? - fprintf(output_file, " size => %zu,", data_size); + generate(" size => %zu,", data_size); if (name) { // this should be a parm_decl with an identifier of the name @@ -545,11 +568,11 @@ static void export_params(tree func) { } if (!names || !names[0]) { - sprintf(nameb, "arg_%d", id); + sprintf(nameb, "arg$%d", id); names = nameb; } - fprintf(output_file, " name => '%s',", names); + generate(" name => '%s',", names); stack_push(&context_stack, node_alloc(param, names)); // value: details @@ -557,7 +580,7 @@ static void export_params(tree func) { free(stack_pull(&context_stack)); - fprintf(output_file, "},\n"); + generate("},\n"); id++; } @@ -585,7 +608,7 @@ static void export_fields(tree first_field, size_t base_offset, int indent) { if (debug_level > 1) fprintf(stderr, " field: %s\n", names); print_spaces(indent+1); - fprintf(output_file, "{ name => '%s', size => %zu, offset => %zu,", names, field_size, offset); + generate("{ name => '%s', size => %zu, offset => %zu,", names, field_size, offset); stack_push(&context_stack, node_alloc(field, names)); // value: details @@ -593,7 +616,7 @@ static void export_fields(tree first_field, size_t base_offset, int indent) { free(stack_pull(&context_stack)); - fprintf(output_file, "},\n"); + generate("},\n"); } } } @@ -630,7 +653,7 @@ static void export_type(tree type, const char *names) { case FUNCTION_TYPE: { // function pointer typdef // I don't know if i even want this - fprintf(output_file, "'call:%s' => { name => '%s', type => 'call',", names, names); + generate("'call:%s' => { name => '%s', type => 'call',", names, names); // the deftype is always a pointer for a function_type @@ -638,24 +661,24 @@ static void export_type(tree type, const char *names) { buffer_init(&b, 256); export_desc(type, deftype, &b); - fprintf(output_file, " deref => '%s',", b.data); + generate(" deref => '%s',", b.data); free(b.data); - fprintf(output_file, " ctype => '%s',", print_generic_expr_to_str(target)); + generate(" ctype => '%s',", print_generic_expr_to_str(target)); // TODO: cleanup { tree result_type = TREE_TYPE(target); const size_t data_size = value_size(TYPE_SIZE(result_type)); - fprintf(output_file, "\n\tresult => {"); + generate("\n\tresult => {"); export_param(target, result_type, data_size); - fprintf(output_file, " },"); + generate(" },"); } - fprintf(output_file, "\n\targuments => [\n"); + generate("\n\targuments => [\n"); export_params(target); - fprintf(output_file, "]},\n"); + generate("]},\n"); break; } case ENUMERAL_TYPE: { @@ -670,15 +693,15 @@ static void export_type(tree type, const char *names) { return; } - fprintf(output_file, "'enum:%s' => { name => '%s', type => 'enum', size => %zu, value_type => '%c%zu', values => [\n", + generate("'enum:%s' => { name => '%s', type => 'enum', size => %zu, value_type => '%c%zu', values => [\n", names, names, size, TYPE_UNSIGNED(target) ? 'u' : 'i', size); for (tree v = TYPE_VALUES(target); v != NULL; v = TREE_CHAIN (v)) { - fprintf(output_file, "\t{ label => '%s', value => '%ld' },\n", + generate("\t{ label => '%s', value => '%ld' },\n", IDENTIFIER_POINTER(TREE_PURPOSE(v)), tree_to_shwi(TREE_VALUE(v))); } - fprintf(output_file, "]},\n"); + generate("]},\n"); break; } case RECORD_TYPE: // forward declaration or opaque types @@ -710,12 +733,12 @@ static void export_type(tree type, const char *names) { if (debug_level > 1) fprintf(stderr, "export type func decl %s\n", names); - fprintf(output_file, "'func:%s' => { name => '%s', type => 'func',", names, names); + generate("'func:%s' => { name => '%s', type => 'func',", names, names); // FUNCTION_DECL -> FUNCTION_TYPE -> RESULT_TYPE, get FUNCTION_TYPE type = TREE_TYPE(type); - fprintf(output_file, " ctype => '%s',", print_generic_expr_to_str(type)); + generate(" ctype => '%s',", print_generic_expr_to_str(type)); // TODO: cleanup debug_tree_helper(type, "function 1"); @@ -723,15 +746,15 @@ static void export_type(tree type, const char *names) { tree result_type = TREE_TYPE(type); const size_t data_size = value_size(TYPE_SIZE(result_type)); - fprintf(output_file, "\n\tresult => {"); + generate("\n\tresult => {"); export_param(type, result_type, data_size); - fprintf(output_file, " },"); + generate(" },"); } - fprintf(output_file, "\n\targuments => [\n"); + generate("\n\targuments => [\n"); //export_decl_params(DECL_ARGUMENTS(type), 0); export_params(type); - fprintf(output_file, "]},\n"); + generate("]},\n"); break; } case FUNCTION_TYPE: { @@ -750,8 +773,8 @@ static void export_type(tree type, const char *names) { if (debug_level > 1) fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type)); - fprintf(output_file, "'call:%s' => { name => '%s', type => 'call',", names, names); - fprintf(output_file, " ctype => '%s',", print_generic_expr_to_str(type)); + generate("'call:%s' => { name => '%s', type => 'call',", names, names); + generate(" ctype => '%s',", print_generic_expr_to_str(type)); debug_tree_helper(type, "function type"); @@ -765,16 +788,16 @@ static void export_type(tree type, const char *names) { if (debug_level > 2) fprintf(stderr, " result size %zu\n", data_size); - fprintf(output_file, "\n\tresult => {"); + generate("\n\tresult => {"); export_param(type, result, data_size); - fprintf(output_file, " },"); + generate(" },"); } stack_push(&context_stack, node_alloc(type, names)); - fprintf(output_file, "\n\targuments => [\n"); + generate("\n\targuments => [\n"); export_params(type); free(stack_pull(&context_stack)); - fprintf(output_file, "]},\n"); + generate("]},\n"); break; } case RECORD_TYPE: // struct @@ -798,14 +821,14 @@ static void export_type(tree type, const char *names) { if (debug_level > 1) fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type)); - fprintf(output_file, "'%s:%s' => { name => '%s', type => '%s', size => %zu, fields => [\n", + generate("'%s:%s' => { name => '%s', type => '%s', size => %zu, fields => [\n", su, names, names, su, tree_to_uhwi(TYPE_SIZE(type))); stack_push(&context_stack, node_alloc(type, names)); export_fields(TYPE_FIELDS(type), 0, 0); free(stack_pull(&context_stack)); - fprintf(output_file, "]},\n"); + generate("]},\n"); break; } case ENUMERAL_TYPE: { @@ -846,15 +869,15 @@ static void export_type(tree type, const char *names) { if (debug_level > 1) fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type)); - fprintf(output_file, "'enum:%s' => { name => '%s', type => 'enum', size => %zu, value_type => '%c%zu', values => [\n", + generate("'enum:%s' => { name => '%s', type => 'enum', size => %zu, value_type => '%c%zu', values => [\n", names, names, size, TYPE_UNSIGNED(type) ? 'u' : 'i', size); for (tree v = TYPE_VALUES(type); v != NULL; v = TREE_CHAIN (v)) { - fprintf(output_file, "\t{ label => '%s', value => '%ld' },\n", + generate("\t{ label => '%s', value => '%ld' },\n", IDENTIFIER_POINTER(TREE_PURPOSE(v)), tree_to_shwi(TREE_VALUE(v))); } - fprintf(output_file, "]},\n"); + generate("]},\n"); break; } case FIELD_DECL: @@ -879,7 +902,8 @@ static void export_type(tree type, const char *names) { // it's keyed on target struct node *fwd = node_alloc(target, NULL); - fprintf(stderr, "save forward reference function type %p\n", target); + if (debug_level > 0) + fprintf(stderr, "save forward reference function type %p\n", target); for (tree param = TYPE_ARG_TYPES(target); param != NULL; param = TREE_CHAIN(param)) { tree param_type = TREE_VALUE(param); @@ -889,16 +913,18 @@ static void export_type(tree type, const char *names) { struct node *decl = stack_pull(¶meters); if (decl) { - fprintf(stderr, "(pull parameter '%s')\n", decl->name); + if (debug_level > 0) + fprintf(stderr, "(pull parameter '%s')\n", decl->name); stack_push(&fwd->list, decl); } else - fprintf(stderr, " missing parameter name\n"); + fprintf(stderr, "WARNING: stack is missing parameter name function %s\n", names); } hash_put_bytype(&forward_types, fwd); } - fprintf(stderr, "(push parameter '%s')\n", names); + if (debug_level > 0) + fprintf(stderr, "(push parameter '%s')\n", names); stack_push(¶meters, node_alloc(type, names)); break; } @@ -935,7 +961,8 @@ static void plugin_finish_decl(void *event_data, void *user_data) { } static void plugin_finish(void *event_data, void *user_data) { - fprintf(stderr, "plugin finish\n"); + if (debug_level > 0) + fprintf(stderr, "plugin finish\n"); for (struct node *n = todump.head; n; n=n->next) { if (COMPLETE_TYPE_P(n->type)) { if (n->name[0]) { @@ -946,14 +973,15 @@ static void plugin_finish(void *event_data, void *user_data) { } } - fprintf(output_file, "# dumped structs:\n"); + generate("# dumped structs:\n"); for (struct node *n = dumped.list.head; n; n=n->next) - fprintf(output_file, "# %s\n", n->name); + generate("# %s\n", n->name); - fprintf(output_file, ");\n"); + generate(");\n"); fclose(output_file); - fprintf(stderr, "unhandled paramters:\n"); + if (debug_level > 0) + fprintf(stderr, "unhandled paramters:\n"); list_clear(¶meters); } @@ -979,7 +1007,7 @@ int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version exit(EXIT_FAILURE); } - fprintf(output_file, "%%data = (\n"); + generate("%%data = (\n"); 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); diff --git a/src/generate b/src/generate deleted file mode 100755 index e6eaf8c..0000000 --- a/src/generate +++ /dev/null @@ -1,852 +0,0 @@ -#!/usr/bin/perl - -# usage -# generate [-d dir] [-t package] [--enclosing-type type ] [-s struct-root-pattern]* [--struct-file file]* [-c class [-llib]* [-f func-pattern]* [--func-file file]* [-e enum-pattern]]* -# -d dir -# root output directory -# -t package -# output package -# --enclosing-type type -# If supplied, all structures and classes are written to an enclosing class -# -s struct-root-pattern -# provide one or more patterns for matching structure roots. -# all dependencies are automatically included. -# if no pattern is provided, all match. -# --struct-file file -# provide a filename with exact structure names in it. These are -# used as roots(?) -# -c class -# specify class name to generate -# -llib -# specify link library used by class -# -f func-pattern -# function name pattern to include for the current class -# --func-file file -# point to a filename with exact function names in it, one per line ('#' is a comment). -# -e enum-pattern -# enum name pattern to include for the current class -# --enum-file file -# filename with enums in it - -# TODO: scan all functions and include any types they use as struct roots -# TODO: some way to specify external types - -@matchStruct = (); -$meta = ""; -# @classes = ( { name => 'class', match => [ func-pattern, ... ], match_file => [ file, ... ], enum => [ enum-pattern, ... ], enum_file => [ file, ...] } ) -@classes = (); -%class = (); -$output = "."; -# map call signatures to a class name -%callMap = (); -$package = ""; - -while (@ARGV) { - my $cmd = shift(@ARGV); - - if ($cmd eq "-f") { - my $v = shift(@ARGV); - push @{$class{match}}, qr/$v/; - } elsif ($cmd eq "--func-file") { - my $file = shift(@ARGV); - - push @{$class{match_file}}, $file; - push @{$class{match}}, readMatchFile($file); - } elsif ($cmd eq "-e") { - my $v = shift(@ARGV); - push @{$class{enum}}, qr/$v/; - } elsif ($cmd eq "--enum-file") { - my $file = shift(@ARGV); - push @{$class{enum_file}}, $file; - push @{$class{enum}}, readMatchFile($file); - } elsif ($cmd eq "-s") { - my $v = shift(@ARGV); - push @matchStruct, qr/$v/; - } elsif ($cmd eq "--struct-file") { - my $file = shift(@ARGV); - push @matchStruct, readMatchFile($file); - } elsif ($cmd eq "-t") { - $package = shift(@ARGV); - } elsif ($cmd eq "-c") { - my %new = ( - name => shift(@ARGV), - match => [], - match_file => [], - enum => [], - enum_file => [], - libs => []); - push @classes, \%new; - %class = %new; - print "new:\n".Dumper(\%class); - } elsif ($cmd =~ m/^-l(.*)/) { - push @{$class{libs}}, $1; - } elsif ($cmd eq "-d") { - $output = shift(@ARGV); - } elsif ($cmd eq "--enclosing-type") { - $enclosingType = shift(@ARGV); - } else { - $meta = $cmd; - } -} - -use Data::Dumper; -#exit 0; - -require $meta; - -# box types for primitives -%map_box = ( - "long" => "Long", - "int" => "Integer", - "short" => "Short", - "char" => "Character", - "float" => "Float", - "double" => "Double", - "byte" => "Byte", - "void" => "Void" - ); - -sub readMatchFile { - my $path = shift @_; - my @lines = (); - - open(my $f,"<$path"); - while (<$f>) { - chop; - next if m/^#/; - - #push @lines, qr/\^$_\$/; - push @lines, $_; - } - close($f); - - my $all = join ('|', @lines); - - return qr/^($all)$/; -} - -sub camelCase { - my $name = shift @_; - - $name =~ s/_(.)/uc($1)/eg; - - return $name; -} - -sub StudlyCaps { - my $name = shift @_; - - $name =~ s/^(.)/uc($1)/e; - $name =~ s/_(.)/uc($1)/eg; - - return $name; -} - - -sub structSignature { - my %struct = %{shift(@_)}; - my $union = shift(@_); - my $sig = ""; - my @fields = @{$struct{fields}}; - my $offset = 0; - - my $inbf = 0; - my $bfoffset = 0; - my $bfstart = 0; - my $bfsig = ""; - - for $fi (@fields) { - my %field = %{$fi}; - my $off = $field{offset}; - - # bitfields, this only handles 1x u64 bitfield section - # They need to: align to u32/u64 - # Group fields into one full u32/u64 - # TODO: check alignment @ start? - # TODO: clean up and complete - # TODO: bitfields in unions are probably broken - if ($field{ctype} eq 'bitfield') { - if ($inbf) { - if ($off - $offset) { - $bfsig .= "x"; - $bfsig .= ($off - $offset); - } - $bfsig .= $field{type}; - $bfsig .= "($field{name})"; - $offset = $off + $field{size}; - } else { - $inbf = 1; - $bfsig = $field{type}; - $bfsig .= "($field{name})"; - $offset = $off + $field{size}; - $bfstart = $field{offset}; - } - - if ($union) { - $inbf = 0; - - if (($offset - $bfstart) == 32) { - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) < 32) { - $bfsig .= "x"; - $bfsig .= 32 - ($offset - $bfstart); - $offset = $bfstart + 32; - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) == 64) { - $bfsig = "u64=[$bfsig]"; - } elsif (($offset - $bfstart) < 64) { - $bfsig .= "x"; - $bfsig .= 64 - ($offset - $bfstart); - $offset = $bfstart + 64; - $bfsig = "u64=[$bfsig]"; - } - - $sig .= $bfsig; - $sig .= "|" if ($union && $fi != @fields[$#fields]); - } - next; - } elsif ($inbf) { - if (($offset - $bfstart) == 32) { - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) < 32) { - $bfsig .= "x"; - $bfsig .= 32 - ($offset - $bfstart); - $offset = $bfstart + 32; - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) == 64) { - $bfsig = "u64=[$bfsig]"; - } elsif (($offset - $bfstart) < 64) { - $bfsig .= "x"; - $bfsig .= 64 - ($offset - $bfstart); - $offset = $bfstart + 64; - $bfsig = "u64=[$bfsig]"; - } - $sig .= $bfsig; - $inbf = 0; - } - - # skip to next offset if necessary - if ($off > $offset) { - $sig .= "x"; - $sig .= ($off - $offset); - } - $offset = $off + $field{size}; - - # normal field processing - if ($field{deref}) { - my $deref = $field{deref}; - - # HACK: function -> Void - # if ($field{debug} eq 'function') { - # $sig .= "u64($field{name}):v"; - # } els - if ($deref =~ m/^(u\d\d)(:.*)/) { - $sig .= "$1($field{name})$2"; - } else { - $sig .= "$deref($field{name})"; - } - } else { - if ($field{type} =~ m/(struct|union):(.*)/) { - $sig .= "\${$2}"; - } elsif ($field{type} =~ m/([iuf])(\d+)/) { - $sig .= $1; - $sig .= $2; - } elsif ($field{type} eq 'void') { - $sig .= "v"; - } elsif ($field{type} eq 'enum') { - # FIXME: set type in compiler - $sig .= "u32"; - } - - $sig .= "($field{name})"; - } - - $sig .= "|" if ($union && $fi != @fields[$#fields]); - } - - # finish any trailing bitfield - # TODO: cleanup - if ($inbf) { - if (($offset - $bfstart) == 32) { - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) < 32) { - $bfsig .= "x"; - $bfsig .= 32 - ($offset - $bfstart); - $offset = $bfstart + 32; - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) == 64) { - $bfsig = "u64=[$bfsig]"; - } elsif (($offset - $bfstart) < 64) { - $bfsig .= "x"; - $bfsig .= 64 - ($offset - $bfstart); - $offset = $bfstart + 64; - $bfsig = "u64=[$bfsig]"; - } - #$bfsig .= "]"; - $sig .= $bfsig; - } - - return "[".$sig."]"; -} - -sub funcSignature { - my %func = %{shift(@_)}; - my $sig = ""; - my @params = @{$func{arguments}}; - - for $pi (@params) { - my %param = %{$pi}; - - if ($param{deref}) { - # HACK: function to void - if ($param{debug} eq "function") { - $sig .= "u64:v"; - } else { - $sig .= $param{deref}; - } - } else { - if ($param{type} =~ m/struct:(.*)/) { - $sig .= "\${$1}"; - } elsif ($param{type} =~ m/([iuf])(\d*)/) { - $sig .= $1; - $sig .= $2; - } elsif ($param{type} eq "void") { - $sig .= "v"; - } - } - } - - my %result = %{$func{result}}; - my $ret = ""; - - if ($result{deref}) { - $ret .= $result{deref}; - } else { - if ($result{type} =~ m/^struct:(.*)/) { - $ret .= "\${$1}"; - } elsif ($result{type} =~ m/^([iuf])(\d+)/) { - $ret .= $1; - $ret .= $2; - } elsif ($result{type} eq "void") { - $ret .= "v"; - } - } - - return "($sig)$ret"; -} - -sub deref { - my $type = shift @_; - my $ref = shift @_; - - while ($ref) { - if ($ref =~ m/\[\d*(.*)\]/) { - my $sub = deref($type, $1); - - return "Array<$sub>"; - } elsif ($ref =~ m/^u64:(.*)/) { - $type = "Pointer<$type>"; - $ref = $1; - } else { - last; - } - } - return $type; -} - -sub typeToJava { - my %param = %{shift(@_)}; - my $type = $param{type}; - my $ref = $param{deref}; - - if ($type =~ m/^struct:(.*)/) { - $type = StudlyCaps($1); - } elsif ($type =~ m/call:/) { - # this re-writes ref to remove one pointer-to as the Callback absorbs it. - $type = "Callback<".$callMap{$type}.">"; - $type || die ("No mapping for type ".Dumper(\%param)); - $ref =~ s/^u(32|64)://; - } elsif ($type =~ m/^enum:(.*)/) { - # TODO: other enum options - $type = "int"; - } elsif ($type eq "void") { - $type = "void"; - } elsif ($type =~ m/^([iu])(\d*)/) { - my $sign = $1; - my $size = $2; - - if ($size <= 8) { - $type = "byte"; - } elsif ($size <= 16) { - if ($sign eq "i") { - $type = "short"; - } else { - $type = "char"; - } - } elsif ($size <= 32) { - $type = "int"; - } else { - $type = "long"; - } - } elsif ($type =~ m/^[f](\d*)$/) { - my $size = $1; - - if ($size == 32) { - $type = "float"; - } elsif ($size == 64) { - $type = "double"; - } - } - - if ($ref) { - $type = $map_box{$type} if ($map_box{$type}); - $type = deref($type, $ref); - } - - return $type; -} - -sub testMatch { - my $name = shift @_; - - if (@_) { - for $pat (@_) { - if ($name =~ /$pat/) { - return 1; - } - } - return 0; - } else { - return 1; - } -} - -# find all matching structures and then all that they require -sub findStructs { - my %all = %{shift @_}; - my @match = @_; - my @stack = grep { - my %e = %{$all{$_}}; - $e{type} =~ m/(struct|union)/ && testMatch($e{name}, @match); - } keys %all; - my %visit = (); - - while (@stack) { - my $test = shift @stack; - - if (!$visit{$test}) { - my %struct = %{$all{$test}}; - - $visit{$test} = 1; - - if (%struct) { - print "class: $struct{name}\n"; - # find all types this one uses - for $f (@{$struct{fields}}) { - my %field = %{$f}; - - if ($field{type} =~ m/^(struct|union):(.*)/) { - if (!$set{$field{type}}) { - $set{$field{type}} = $all{$field{type}}; - push @stack, $field{type}; - } - } - } - } else { - # this is an anon type, typically used for handles - $test =~ m/^(struct|union):(.*)/; - print " anon: $2\n"; - my %rec = ( - type => 'struct', - name => $2, - size => 0 - ); - $data{$test} = \%rec; - } - } - } - return keys %visit; -} - -sub findDefinition { - my %all = %{shift @_}; - my $type = shift @_; - my @match = @_; - my @stack = grep { - my %e = %{$all{$_}}; - $e{type} eq $type && testMatch($e{name}, @match); - } keys %all; - - return @stack; -} - -# ###################################################################### - -# setup section - -# find all classes used by functions -my %roots = (); -for $c (@classes) { - my %class = %{$c}; - my @libs = @{$class{libs}}; - my @match = @{$class{match}}; - - for $k (findDefinition(\%data, 'func', @match)) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - - for $pi (@params) { - my %param = %{$pi}; - - if ($param{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } - } - - my %result = %{$func{result}}; - - if ($result{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } - } -} - -# add roots for any types used by calls -# FIXME: only include ones used elsewhere -for $k (grep { $_ =~ m/^call:/n } keys %data) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - - for $pi (@params) { - my %param = %{$pi}; - - if ($param{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } - } - - my %result = %{$func{result}}; - - if ($result{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } -} - -# Create anonymous structs for anything missing -for $k (keys %roots) { - my $s = 'struct:'.$k; - my $u = 'union:'.$k; - - if (!$data{$u} && !$data{$s}) { - print " anon: $s\n"; - my %rec = ( - type => 'struct', - name => $k, - size => 0 - ); - $data{$s} = \%rec; - } -} - -$all = join ('|', keys %roots); -if ($all) { - push @matchStruct, qr/^($all)$/; -} -print "structures:\n"; -print Dumper(@matchStruct); - -# make a map for all callbacks (call: type) to generated names -for $c (grep { $_ =~ m/^call:/n } keys %data) { - my $name = $c; - - print "$c\n"; - # enum maybe to int? - - $name =~ s/^call:/Call/; - $name =~ s/\$\{[^\}]*\}/L/g; - $name =~ s/[ui](64|32):/p/g; - $name =~ s/[ui]64/J/g; - $name =~ s/[ui]32/I/g; - $name =~ s/[ui]8/B/g; - $name =~ s/f32/F/g; - $name =~ s/f64/D/g; - $name =~ s/[\[\]\(\)]/_/g; - - $callMap{$c} = "$name"; -} - -print "call mappings\n"; -print Dumper(\%callMap); - -# ###################################################################### -# Start output -my $dst; - -use File::Basename; -use File::Path qw(make_path); - -if ($package ne "") { - $packagePrefix = $package."."; -} - -if ($enclosingType) { - my $classname = $packagePrefix.$enclosingType; - - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - - print "path $path\n"; - print "dirname $dir\n"; - - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - - print $dst <$path"); - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - print $dst < {\n"; - - for $fi (@fields) { - my %field = %{$fi}; - my $type = typeToJava(\%field); - my $cc = StudlyCaps($field{name}); - - print $dst "\t\@NativeGetter(value=\"$field{name}\")\n"; - print $dst "\tpublic $type get$cc();\n"; - - print $dst "\t\@NativeSetter(value=\"$field{name}\")\n"; - print $dst "\tpublic void set$cc($type value);\n"; - } - - print $dst "}\n"; - - if (!$enclosingType) { - close($dst); - } -} - -# Dump classes for library linkage -for $c (@classes) { - my %class = %{$c}; - my @libs = @{$class{libs}}; - my @match = @{$class{match}}; - - if (!$enclosingType) { - my $classname = $packagePrefix.$class{name}; - - open ($dst, ">$path"); - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - print $dst < 32) - } - - print $dst "\n\t// enum $enum{name}\n"; - for $vi (@values) { - my %value = %{$vi}; - - if (!$visited{$value{label}}) { - #print $dst "\tpublic static final $type $value{label} = ($type)$value{value};\n"; - print $dst "\tpublic static final $type $value{label} = $value{value};\n"; - $visited{$value{label}} = 1; - } - } - } - - # functions - print "class $class{name} -> match:\n".Dumper(\@match); - - for $k (sort(findDefinition(\%data, 'func', @match))) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - my $signature = funcSignature(\%func); - my $name = ($func{name}); - my $result = typeToJava(\%{$func{result}}); - - print $dst "\n\t\@NativeFunction(value=\"$signature\")\n"; - print $dst "\tpublic $result $name("; - - for $pi (@params) { - my %param = %{$pi}; - my $type = typeToJava($pi); - - print $dst "$type $param{name}"; - print $dst ", " if ($pi != $params[$#params]); - } - - print $dst ");\n"; - } - - print $dst "\n"; - print $dst "\tpublic static final $class{name} bind = Libraries.bind(MethodHandles.lookup(), $class{name}.class);\n"; - - print $dst "}\n"; - - if (!$enclosingType) { - close($dst); - } -} - -# Dump callbacks -# TODO: only those used by classes and functions that were exported -for $c (keys %callMap) { - my %call = %{$data{$c}}; - my $name = $callMap{$c}; - my @params = @{$call{arguments}}; - my $result = typeToJava(\%{$call{result}}); - - if (!$enclosingType) { - my $classname = $packagePrefix.$name; - - open ($dst, ">$path"); - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - print $dst < 'class', match => [ func-pattern, ... ], match_file => [ file, ... ], enum => [ enum-pattern, ... ], enum_file => [ file, ...] } ) -@classes = (); -%class = (); -$output = "."; -# map call signatures to a class name -%callMap = (); -$package = ""; -%replace = (); -# calls take raw types and throw Throwable -$rawCalls = 0; - -while (@ARGV) { - my $cmd = shift(@ARGV); - - if ($cmd eq "-f") { - my $v = shift(@ARGV); - push @{$class{match}}, qr/$v/; - } elsif ($cmd eq "--func-file") { - my $file = shift(@ARGV); - - push @{$class{match_file}}, $file; - push @{$class{match}}, readMatchFile($file); - } elsif ($cmd eq "-e") { - my $v = shift(@ARGV); - push @{$class{enum}}, qr/$v/; - } elsif ($cmd eq "--enum-file") { - my $file = shift(@ARGV); - push @{$class{enum_file}}, $file; - push @{$class{enum}}, readMatchFile($file); - } elsif ($cmd eq "-s") { - my $v = shift(@ARGV); - push @matchStruct, qr/$v/; - } elsif ($cmd eq "--struct-file") { - my $file = shift(@ARGV); - push @matchStruct, readMatchFile($file); - } elsif ($cmd eq "-r") { - my $v = shift(@ARGV); - - $v =~ m/(.*)=(.*)/; - $replace{$1} = $2; - } elsif ($cmd eq "--raw-calls") { - $rawCalls = 1; - } elsif ($cmd eq "-t") { - $package = shift(@ARGV); - } elsif ($cmd eq "-c") { - my %new = ( - name => shift(@ARGV), - match => [], - match_file => [], - enum => [], - enum_file => [], - libs => []); - push @classes, \%new; - %class = %new; - print "new:\n".Dumper(\%class); - } elsif ($cmd =~ m/^-l(.*)/) { - push @{$class{libs}}, $1; - } elsif ($cmd eq "-d") { - $output = shift(@ARGV); - } elsif ($cmd eq "--enclosing-type") { - $enclosingType = shift(@ARGV); - } else { - $meta = $cmd; - } -} - -$importPointer = "import api.Native.Pointer;" if (!$rawCalls); - -print "import poirnter: $importPointer\n"; -use Data::Dumper; - -require $meta; - -# box types for primitives -%map_box = ( - "long" => "Long", - "int" => "Integer", - "short" => "Short", - "char" => "Character", - "float" => "Float", - "double" => "Double", - "byte" => "Byte", - "void" => "Void" - ); - -sub readMatchFile { - my $path = shift @_; - my @lines = (); - - open(my $f,"<$path"); - while (<$f>) { - chop; - next if m/^#/; - - #push @lines, qr/\^$_\$/; - push @lines, $_; - } - close($f); - - my $all = join ('|', @lines); - - return qr/^($all)$/; -} - -sub camelCase { - my $name = shift @_; - - $name =~ s/_(.)/uc($1)/eg; - - return $name; -} - -sub StudlyCaps { - my $name = shift @_; - - # hack, or good spot for it? - return $replace{$name} if $replace{$name}; - - $name =~ s/^(.)/uc($1)/e; - $name =~ s/_(.)/uc($1)/eg; - - return $name; -} - - -sub structSignature { - my %struct = %{shift(@_)}; - my $union = shift(@_); - my $sig = ""; - my @fields = @{$struct{fields}}; - my $offset = 0; - - my $inbf = 0; - my $bfoffset = 0; - my $bfstart = 0; - my $bfsig = ""; - - for $fi (@fields) { - my %field = %{$fi}; - my $off = $field{offset}; - - # bitfields, this only handles 1x u64 bitfield section - # They need to: align to u32/u64 - # Group fields into one full u32/u64 - # TODO: check alignment @ start? - # TODO: clean up and complete - # TODO: bitfields in unions are probably broken - if ($field{ctype} eq 'bitfield') { - if ($inbf) { - if ($off - $offset) { - $bfsig .= "x"; - $bfsig .= ($off - $offset); - } - $bfsig .= $field{type}; - $bfsig .= "($field{name})"; - $offset = $off + $field{size}; - } else { - $inbf = 1; - $bfsig = $field{type}; - $bfsig .= "($field{name})"; - $offset = $off + $field{size}; - $bfstart = $field{offset}; - } - - if ($union) { - $inbf = 0; - - if (($offset - $bfstart) == 32) { - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) < 32) { - $bfsig .= "x"; - $bfsig .= 32 - ($offset - $bfstart); - $offset = $bfstart + 32; - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) == 64) { - $bfsig = "u64=[$bfsig]"; - } elsif (($offset - $bfstart) < 64) { - $bfsig .= "x"; - $bfsig .= 64 - ($offset - $bfstart); - $offset = $bfstart + 64; - $bfsig = "u64=[$bfsig]"; - } - - $sig .= $bfsig; - $sig .= "|" if ($union && $fi != @fields[$#fields]); - } - next; - } elsif ($inbf) { - if (($offset - $bfstart) == 32) { - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) < 32) { - $bfsig .= "x"; - $bfsig .= 32 - ($offset - $bfstart); - $offset = $bfstart + 32; - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) == 64) { - $bfsig = "u64=[$bfsig]"; - } elsif (($offset - $bfstart) < 64) { - $bfsig .= "x"; - $bfsig .= 64 - ($offset - $bfstart); - $offset = $bfstart + 64; - $bfsig = "u64=[$bfsig]"; - } - $sig .= $bfsig; - $inbf = 0; - } - - # skip to next offset if necessary - if ($off > $offset) { - $sig .= "x"; - $sig .= ($off - $offset); - } - $offset = $off + $field{size}; - - # normal field processing - if ($field{deref}) { - my $deref = $field{deref}; - - # HACK: function -> Void - # if ($field{debug} eq 'function') { - # $sig .= "u64($field{name}):v"; - # } els - if ($deref =~ m/^(u\d\d)(:.*)/) { - $sig .= "$1($field{name})$2"; - } else { - $sig .= "$deref($field{name})"; - } - } else { - if ($field{type} =~ m/(struct|union):(.*)/) { - $sig .= "\${$2}"; - } elsif ($field{type} =~ m/([iuf])(\d+)/) { - $sig .= $1; - $sig .= $2; - } elsif ($field{type} eq 'void') { - $sig .= "v"; - } elsif ($field{type} eq 'enum') { - # FIXME: set type in compiler - $sig .= "u32"; - } - - $sig .= "($field{name})"; - } - - $sig .= "|" if ($union && $fi != @fields[$#fields]); - } - - # finish any trailing bitfield - # TODO: cleanup - if ($inbf) { - if (($offset - $bfstart) == 32) { - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) < 32) { - $bfsig .= "x"; - $bfsig .= 32 - ($offset - $bfstart); - $offset = $bfstart + 32; - $bfsig = "u32=[$bfsig]"; - } elsif (($offset - $bfstart) == 64) { - $bfsig = "u64=[$bfsig]"; - } elsif (($offset - $bfstart) < 64) { - $bfsig .= "x"; - $bfsig .= 64 - ($offset - $bfstart); - $offset = $bfstart + 64; - $bfsig = "u64=[$bfsig]"; - } - #$bfsig .= "]"; - $sig .= $bfsig; - } - - return "[".$sig."]"; -} - -sub funcSignature { - my %func = %{shift(@_)}; - my $sig = ""; - my @params = @{$func{arguments}}; - - for $pi (@params) { - my %param = %{$pi}; - - if ($param{deref}) { - # HACK: function to void - if ($param{debug} eq "function") { - $sig .= "u64:v"; - } else { - $sig .= $param{deref}; - } - } else { - if ($param{type} =~ m/struct:(.*)/) { - $sig .= "\${$1}"; - } elsif ($param{type} =~ m/([iuf])(\d*)/) { - $sig .= $1; - $sig .= $2; - } elsif ($param{type} eq "void") { - $sig .= "v"; - } - } - } - - my %result = %{$func{result}}; - my $ret = ""; - - if ($result{deref}) { - $ret .= $result{deref}; - } else { - if ($result{type} =~ m/^struct:(.*)/) { - $ret .= "\${$1}"; - } elsif ($result{type} =~ m/^([iuf])(\d+)/) { - $ret .= $1; - $ret .= $2; - } elsif ($result{type} eq "void") { - $ret .= "v"; - } - } - - return "($sig)$ret"; -} - -sub deref { - my $type = shift @_; - my $ref = shift @_; - - while ($ref) { - if ($ref =~ m/\[\d*(.*)\]/) { - my $sub = deref($type, $1); - - return "Array<$sub>"; - } elsif ($ref =~ m/^u64:\$/) { - # ignore penultimate pointer? - last; - } elsif ($ref =~ m/^u64:(.*)/) { - $type = "Pointer<$type>"; - $ref = $1; - } else { - last; - } - } - return $type; -} - -sub typeToJava { - my %param = %{shift(@_)}; - my $type = $param{type}; - my $ref = $param{deref}; - - if ($type =~ m/^struct:(.*)/) { - $type = $replace{$1} ? $replace{$1} : StudlyCaps($1); - } elsif ($type =~ m/call:/) { - # this re-writes ref to remove one pointer-to as the Callback absorbs it. - $type = "Callback<".$callMap{$type}.">"; - $type || die ("No mapping for type ".Dumper(\%param)); - $ref =~ s/^u(32|64)://; - } elsif ($type =~ m/^enum:(.*)/) { - # TODO: other enum options - $type = "int"; - } elsif ($type eq "void") { - $type = "void"; - } elsif ($type =~ m/^([iu])(\d*)/) { - my $sign = $1; - my $size = $2; - - if ($size <= 8) { - $type = "byte"; - } elsif ($size <= 16) { - if ($sign eq "i") { - $type = "short"; - } else { - $type = "char"; - } - } elsif ($size <= 32) { - $type = "int"; - } else { - $type = "long"; - } - } elsif ($type =~ m/^[f](\d*)$/) { - my $size = $1; - - if ($size == 32) { - $type = "float"; - } elsif ($size == 64) { - $type = "double"; - } - } - - if ($ref) { - $type = $map_box{$type} if ($map_box{$type}); - $type = deref($type, $ref); - } - - return $type; -} - -sub typeToRaw { - my %param = %{shift(@_)}; - my $type = $param{type}; - my $ref = $param{deref}; - - my $type = typeToJava(\%param); - - if ($ref =~ m/^u64:/) { - return "MemoryAddress"; - } elsif ($type =~ m/^(struct|union):/) { - return "MemorySegment"; - } else { - return $type; - } - - # hackity hack -# if ($type =~ "(Pointer|Array|Callback)") { -# return "MemoryAddress"; -# } elsif ($type =~ m/^[A-Z]/) { -# return "MemorySegment"; -# } else { -# return $type; -# } -} - -sub testMatch { - my $name = shift @_; - - if (@_) { - for $pat (@_) { - if ($name =~ /$pat/) { - return 1; - } - } - return 0; - } else { - return 1; - } -} - -# find all matching structures and then all that they require -sub findStructs { - my %all = %{shift @_}; - my @match = @_; - my @stack = grep { - my %e = %{$all{$_}}; - $e{type} =~ m/(struct|union)/ && !$replace{$e{name}} && testMatch($e{name}, @match); - } keys %all; - my %visit = (); - - while (@stack) { - my $test = shift @stack; - - if (!$visit{$test}) { - my %struct = %{$all{$test}}; - - $visit{$test} = 1; - - if (%struct) { - print "class: $struct{name}\n"; - # find all types this one uses - for $f (@{$struct{fields}}) { - my %field = %{$f}; - - if ($field{type} =~ m/^(struct|union):(.*)/) { - if (!$replace{$1} && !$set{$field{type}}) { - $set{$field{type}} = $all{$field{type}}; - push @stack, $field{type}; - } - } - } - } else { - # this is an anon type, typically used for handles - $test =~ m/^(struct|union):(.*)/; - if (!$replace{$2}) { - print " anon: $2\n"; - my %rec = ( - type => 'struct', - name => $2, - size => 0 - ); - $data{$test} = \%rec; - } - } - } - } - return grep { !$replace{$_} } keys(%visit); -} - -sub findDefinition { - my %all = %{shift @_}; - my $type = shift @_; - my @match = @_; - my @stack = grep { - my %e = %{$all{$_}}; - $e{type} eq $type && testMatch($e{name}, @match); - } keys %all; - - return @stack; -} - -sub arrayInfo { - my $ref = shift @_; - my %info = ( - dims => [], - ); - - print "array $ref\n"; - while ($ref =~ m/^\[(\d*)(.*)\]$/) { - push @{$info{dims}}, $1; - $ref = $2; - print "dim $1 -, '$2'\n"; - } - $info{deref} = $ref; - - return %info; -} - -# ###################################################################### - -# setup section - -# find all classes used by functions -my %roots = (); -for $c (@classes) { - my %class = %{$c}; - my @libs = @{$class{libs}}; - my @match = @{$class{match}}; - - for $k (findDefinition(\%data, 'func', @match)) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - - for $pi (@params) { - my %param = %{$pi}; - - if ($param{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } - } - - my %result = %{$func{result}}; - - if ($result{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } - } -} - -# add roots for any types used by calls -# FIXME: only include ones used elsewhere -for $k (grep { $_ =~ m/^call:/n } keys %data) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - - for $pi (@params) { - my %param = %{$pi}; - - if ($param{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } - } - - my %result = %{$func{result}}; - - if ($result{type} =~ m/^(struct|union):(.*)/) { - $roots{$2} = 1; - } -} - -# Create anonymous structs for anything missing -for $k (keys %roots) { - my $s = 'struct:'.$k; - my $u = 'union:'.$k; - - if (!$data{$u} && !$data{$s} && !$replace{$k}) { - print " xanon: $s\n"; - my %rec = ( - type => 'struct', - name => $k, - size => 0 - ); - $data{$s} = \%rec; - } -} - -$all = join ('|', keys %roots); -if ($all) { - push @matchStruct, qr/^($all)$/; -} -print "structures:\n"; -print Dumper(@matchStruct); - -# make a map for all callbacks (call: type) to generated names -for $c (grep { $_ =~ m/^call:/n } keys %data) { - my $name = $c; - - print "$c\n"; - # enum maybe to int? - - $name =~ s/^call:/Call/; - if ($rawCalls) { - $name =~ s/\$\{([^\}]*)\}/L/g; - } else { - while ($name =~ m/\$\{([^\}]*)\}/) { - my $x = $1; - if ($replace{$x}) { - $x = $replace{$x}; - } else { - $x = StudlyCaps($x); - } - $name =~ s/\$\{([^\}]*)\}/L$x/; - } - } - $name =~ s/[ui](64|32):/p/g; - $name =~ s/[ui]64/J/g; - $name =~ s/[ui]32/I/g; - $name =~ s/[ui]8/B/g; - $name =~ s/f32/F/g; - $name =~ s/f64/D/g; - $name =~ s/[\[\]\(\)]/_/g; - - $callMap{$c} = "$name"; -} - -print "call mappings\n"; -print Dumper(\%callMap); - -# ###################################################################### -# Start output -my $dst; - -use File::Basename; -use File::Path qw(make_path); - -if ($package ne "") { - $packagePrefix = $package."."; -} - -if ($enclosingType) { - my $classname = $packagePrefix.$enclosingType; - - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - - print "path $path\n"; - print "dirname $dir\n"; - - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - - print $dst <$path"); - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - print $dst <> 3; - my $addr = $offset ? "addr().addOffset($offset)" : 'addr()'; - - my $size = %{$data{$field{type}}}{size} >> 3; - - print $dst "\tpublic $ltype get$cc() {\n"; - print $dst "\t\treturn $ltype.create(Native.getAddr($addr, $size));\n"; - print $dst "\t}\n"; - - print $dst "\tpublic void set$cc($ltype v) {\n"; - print $dst "\t\tNative.setAddr($addr, v.addr());\n"; - print $dst "\t}\n"; - } - } elsif ($field{deref} =~ m/^u64:u64:\$/) { - # pointer-to-pointer-to? - if ($field{type} =~ m/^(struct|union):(.*)/) { - my $ltype = StudlyCaps($2); - my $offset = $field{offset} >> 3; - my $addr = $offset ? "addr().addOffset($offset)" : 'addr()'; - - my $size = %{$data{$field{type}}}{size} >> 3; - - print $dst "\tpublic $type get$cc() {\n"; - print $dst "\t\treturn Native.Pointer.ofAddress($addr, $size, $ltype"."::new);\n"; - print $dst "\t}\n"; - - print $dst "\tpublic void set$cc($type v) {\n"; - print $dst "\t\tNative.setAddr($addr, v.addr());\n"; - print $dst "\t}\n"; - } - } elsif ($field{ctype} eq 'bitfield') { - my $alsr = $field{type} =~ m/^u/ ? '>>>' : '>>'; - my $lshift = $field{size} <= 32 ? 5 : 6; - my $lbits = 1 << $lshift; - my $type = $lbits == 32 ? 'int' : 'long'; - my $ltype = $lbits == 32 ? 'Int' : 'Long'; - - my $offset = ($field{offset} >> ($lshift)) * ($lbits / 8); - my $addr = $offset ? "addr().addOffset($offset)" : 'addr()'; - my $shift = $field{offset} & ($lbits-1); - my $width = $field{size}; - my $upshift = ($lbits-$width-$shift); - my $downshift = ($lbits-$width); - my $mask = sprintf("0x%x", ((1 << $width) - 1) << $shift); - - print $dst "\tpublic $type get$cc() {\n"; - print $dst "\t\treturn (($type)Native.get$ltype($addr)) << $upshift $alsr $downshift;\n"; - print $dst "\t}\n"; - - print $dst "\tpublic void set$cc($type v) {\n"; - print $dst "\t\tMemoryAddress addr = $addr;\n"; - print $dst "\t\tNative.set$ltype(addr, ((($type)Native.get$ltype(addr)) & ~$mask) | ((v << $shift) & $mask));\n"; - print $dst "\t}\n"; - } elsif ($field{type} =~ m/^(struct|union):/) { - # embedded struct - } elsif ($field{type} =~ m/^call:/) { - # call, function? - print $dst "// call? $type $cc\n"; - my $offset = $field{offset} >> 3; - my $addr = $offset ? "addr().addOffset($offset)" : 'addr()'; - my $ltype = $type; - - $type =~ s/Callback<(.*)>/$1/; - - print $dst "\tprivate Pointer<$type> $cc;\n"; - - print $dst "\tpublic void set$cc($type v) {\n"; - print $dst "\t\tif ($cc != null) $cc.close();\n"; - print $dst "\t\tNative.setAddr($addr, ($cc = $type.call(v)).addr());\n"; - print $dst "\t}\n"; - } else { - my $offset = $field{offset} >> 3; - my $addr = $offset ? "addr().addOffset($offset)" : 'addr()'; - my $ltype = $type; - - $ltype =~ s/^(.)/uc($1)/e; - - die("non-byte offset=$offset ".Dumper(\%field)) if ($field{offset} & 7); - - print $dst "\tpublic $type get$cc() {\n"; - print $dst "\t\treturn Native.get$ltype($addr);\n"; - print $dst "\t}\n"; - - print $dst "\tpublic void set$cc($type v) {\n"; - print $dst "\t\tNative.set$ltype($addr, v);\n"; - print $dst "\t}\n"; - } - } - - my $byteSize = $struct{size} >> 3; - print $dst "\tpublic static final long sizeof = $byteSize;\n"; - - # TODO: optional just call new() - print $dst "\tpublic static $name create(MemoryAddress p) {\n"; - print $dst "\t\treturn Native.resolve(p, $name"."::new);\n"; - print $dst "\t}\n"; - - print $dst "\tpublic static $name alloc() {\n"; - print $dst "\t\treturn $name.create(MemorySegment.allocateNative(sizeof).baseAddress());\n"; - print $dst "\t}\n"; - print $dst "\tpublic static Pointer<$name> alloc(int n) {\n"; - print $dst "\t\treturn Pointer.alloc(n, sizeof, $name"."::new);\n"; - print $dst "\t}\n"; - - if ($struct{type} eq "union") { - print $dst "\tpublic static MemoryLayout layout() { return Native.parseUnion(\"$signature\"); }\n"; - } else { - print $dst "\tpublic static MemoryLayout layout() { return Native.parseStruct(\"$signature\"); }\n"; - } - - print $dst "}\n"; - - if (!$enclosingType) { - close($dst); - } -} - -# ###################################################################### -# Dump classes for library linkage -for $c (@classes) { - my %class = %{$c}; - my @libs = @{$class{libs}}; - my @match = @{$class{match}}; - - if (!$enclosingType) { - my $classname = $packagePrefix.$class{name}; - - open ($dst, ">$path"); - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - print $dst < 32) - } - - print $dst "\n\t// enum $enum{name}\n"; - for $vi (@values) { - my %value = %{$vi}; - - if (!$visited{$value{label}}) { - #print $dst "\tpublic static final $type $value{label} = ($type)$value{value};\n"; - print $dst "\tpublic static final $type $value{label} = $value{value};\n"; - $visited{$value{label}} = 1; - } - } - } - - # function handles - print "class $class{name} -> match:\n".Dumper(\@match); - - for $k (sort(findDefinition(\%data, 'func', @match))) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - my $signature = funcSignature(\%func); - my $name = ($func{name}); - - print $dst "\tfinal static MethodHandle $name;\n"; - } - - # function handle init - print $dst "\tstatic {\n"; - print $dst "\t\tLibraryLookup[] libs = Native.loadLibraries(libraries);\n"; - - for $k (sort(findDefinition(\%data, 'func', @match))) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - my $signature = funcSignature(\%func); - my $name = ($func{name}); - - print $dst "\t\t$name = Native.downcallHandle(libs, \"$name\", \"$signature\");\n"; - } - print $dst "\t}\n"; - - # function handle invocation - if ($rawCalls) { - for $k (sort(findDefinition(\%data, 'func', @match))) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - my $signature = funcSignature(\%func); - my $name = ($func{name}); - my %res = %{$func{result}}; - my $result = typeToRaw(\%res); - - print $dst "\tpublic static $result $name("; - - for $pi (@params) { - my %param = %{$pi}; - my $type = typeToRaw($pi); - - print $dst "$type $param{name}"; - print $dst ", " if ($pi != $params[$#params]); - } - - print $dst ") throws Throwable {\n"; - if ($result ne "void") { - print $dst "return ($result)"; - } - print $dst "$name.invokeExact("; - for $pi (@params) { - my %param = %{$pi}; - - print $dst "$param{name}"; - print $dst ", " if ($pi != $params[$#params]); - } - print $dst ");\n"; - print $dst "\t}\n\n"; - } - print $dst "}\n"; - } else { - for $k (sort(findDefinition(\%data, 'func', @match))) { - my %func = %{$data{$k}}; - my @params = @{$func{arguments}}; - my $signature = funcSignature(\%func); - my $name = ($func{name}); - my %res = %{$func{result}}; - my $result = typeToJava(\%{$func{result}}); - - print $dst "\tpublic static $result $name("; - - for $pi (@params) { - my %param = %{$pi}; - my $type = typeToJava($pi); - - $type =~ s/Callback/Pointer/; - - # HACK - $type =~ s/Pointer/Pointer/; - - print $dst "$type $param{name}"; - print $dst ", " if ($pi != $params[$#params]); - } - - print $dst ") {\n"; - # see also call below - print $dst "\t\ttry {\n"; - print $dst "\t\t\t"; - if ($res{type} =~ m/(struct|union)/n) { - if ($res{deref}) { - print $dst "MemoryAddress add = (MemoryAddress)"; - } else { - print $dst "MemorySegment seg = (MemorySegment)"; - } - } elsif ($result ne "void") { - print $dst "return ($result)"; - } - print $dst "$name.invokeExact("; - for $pi (@params) { - my %param = %{$pi}; - - print $dst "$param{name}"; - if ($param{deref}) { - print $dst ".addr()"; - } elsif ($param{type} =~ m/^struct|union/) { - print $dst ".addr().segment()"; - } - print $dst ", " if ($pi != $params[$#params]); - } - print $dst ");\n"; - if ($res{type} =~ m/(struct|union)/n) { - if ($res{deref}) { - print $dst "\t\t\treturn $result.create(add);\n"; - } else { - print $dst "\t\t\treturn $result.create(seg.baseAddress());\n"; - } - } - print $dst "\t\t}\n"; - print $dst "\t\tcatch (Throwable t) { throw new RuntimeException(t); }\n"; - print $dst "\t}\n\n"; - } - - print $dst "}\n"; - } - - if (!$enclosingType) { - close($dst); - } -} - -# ###################################################################### -# Dump callbacks -# TODO: only those used by classes and functions that were exported -# TODO: yeah this is a total total fucking shitshow - -if ($rawCalls) { - for $c (keys %callMap) { - my %call = %{$data{$c}}; - my $name = $callMap{$c}; - my @params = @{$call{arguments}}; - my %res = %{$call{result}}; - my $result = typeToRaw(\%res); - my $signature = funcSignature(\%call); - - if (!$enclosingType) { - my $classname = $packagePrefix.$name; - - open ($dst, ">$path"); - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - print $dst < "; - if ($result ne "void") { - print $dst "($result)"; - } - print $dst "func.invokeExact("; - for $pi (@params) { - my %param = %{$pi}; - - print $dst "$param{name}"; - print $dst ", " if ($pi != $params[$#params]); - } - print $dst ");\n"; - print $dst "\t}\n"; - - # upcall ############################################################## - # ?? - - print $dst "\tstatic MemoryAddress stub($name call) {\n"; - print $dst "\t\treturn Native.upcallStub(MethodHandles.lookup(), call, \"$signature\");\n"; - print $dst "\t}\n"; - - # # the raw interface as expected by the native code - # my $rawresult = typeToRaw(\%res); - # print $dst "\tpublic interface $rawName {\n"; - # # fixme raw result - # print $dst "\t\tpublic $rawresult fn("; - - # for $pi (@params) { - # my %param = %{$pi}; - # my $type = typeToRaw($pi); - - # print $dst "$type $param{name}"; - # print $dst ", " if ($pi != $params[$#params]); - # } - - # print $dst ");\n"; - # print $dst "\t}\n"; - - # print $dst "\tstatic public Pointer<$name> call($name v) {\n"; - # print $dst "\t\t$rawName func = ("; - # for $pi (@params) { - # my %param = %{$pi}; - # my $type = typeToRaw($pi); - - # print $dst "$type $param{name}"; - # print $dst ", " if ($pi != $params[$#params]); - # } - # print $dst ") -> {\n"; - # print $dst "\t\t\t"; - # if ($rawresult ne "void") { - # print $dst "return "; - # } - # print $dst "v.fn("; - # for $pi (@params) { - # my %param = %{$pi}; - # my $type = typeToJava($pi); - # my $rawtype = typeToRaw($pi); - - # print "type ='$type'\n"; - # if ($type =~ m/^Pointer<[^>]*>$/) { - # print $dst "Pointer.ofAddress($param{name})"; - # } elsif ($type eq "Pointer>") { - # print $dst "Pointer.ofAddressP($param{name})"; - # } elsif ($rawtype eq "MemoryAddress") { - # print $dst "$type.create($param{name})"; - # } elsif ($rawtype eq "MemorySegment") { - # print $dst "$type.create($param{name}.baseAddress())"; - # } else { - # print $dst "$param{name}"; - # } - # print $dst ", " if ($pi != $params[$#params]); - # } - # print $dst ")"; - # if ($rawresult eq "MemoryAddress") { - # print $dst ".addr()"; - # } elsif ($rawresult eq "MemorySegment") { - # print $dst ".addr().segment()"; - # } - # print $dst ";\n"; - - # print $dst "\t\t};\n"; - # print $dst "\t\treturn Native.Pointer.ofCallback(MethodHandles.lookup(), v, func, \"$signature\");\n"; - # print $dst "\t}\n"; - - print $dst "}\n"; - - if (!$enclosingType) { - close($dst); - } - } -} else { - for $c (keys %callMap) { - my %call = %{$data{$c}}; - my $name = $callMap{$c}; - my @params = @{$call{arguments}}; - my %res = %{$call{result}}; - my $result = typeToJava(\%{$call{result}}); - my $signature = funcSignature(\%call); - - if (!$enclosingType) { - my $classname = $packagePrefix.$name; - - open ($dst, ">$path"); - $classname =~ s@\.@/@g; - - my $path = $output."/".$classname.".java"; - my $dir = dirname($path); - my $class = basename($path, ".java"); - make_path($dir); - open ($dst, ">$path"); - - if ($package ne "") { - print $dst "package $package;\n"; - } - print $dst < {\n"; - print $dst "\t\t\ttry {\n"; - print $dst "\t\t\t\t"; - if (!$res{deref} && $res{type} =~ m/(struct|union)/n) { - print $dst "MemorySegment seg = (MemorySegment)"; - } elsif ($result ne "void") { - print $dst "return ($result)"; - } - print $dst "func.invokeExact("; - for $pi (@params) { - my %param = %{$pi}; - - print $dst "$param{name}"; - if ($param{deref}) { - print $dst ".addr()"; - } elsif ($param{type} =~ m/^struct|union/) { - print $dst ".addr().segment()"; - } - print $dst ", " if ($pi != $params[$#params]); - } - print $dst ");\n"; - if (!$res{deref} && $res{type} =~ m/(struct|union)/n) { - print $dst "\t\t\t\treturn $result.create(seg.baseAddress());\n"; - } - print $dst "\t\t\t} catch (Throwable t) { throw new RuntimeException(t); }\n"; - print $dst "\t\t};\n"; - print $dst "\t}\n"; - - # upcall ############################################################## - # the raw interface as expected by the native code - my $rawName = $name.'Raw'; - my $rawresult = typeToRaw(\%res); - print $dst "\tpublic interface $rawName {\n"; - # fixme raw result - print $dst "\t\tpublic $rawresult fn("; - - for $pi (@params) { - my %param = %{$pi}; - my $type = typeToRaw($pi); - - print $dst "$type $param{name}"; - print $dst ", " if ($pi != $params[$#params]); - } - - print $dst ");\n"; - print $dst "\t}\n"; - - print $dst "\tstatic public Pointer<$name> call($name v) {\n"; - print $dst "\t\t$rawName func = ("; - for $pi (@params) { - my %param = %{$pi}; - my $type = typeToRaw($pi); - - print $dst "$type $param{name}"; - print $dst ", " if ($pi != $params[$#params]); - } - print $dst ") -> {\n"; - print $dst "\t\t\t"; - if ($rawresult ne "void") { - print $dst "return "; - } - print $dst "v.fn("; - for $pi (@params) { - my %param = %{$pi}; - my $type = typeToJava($pi); - my $rawtype = typeToRaw($pi); - - print "type ='$type'\n"; - if ($type =~ m/^Pointer<[^>]*>$/) { - print $dst "Pointer.ofAddress($param{name})"; - } elsif ($type eq "Pointer>") { - print $dst "Pointer.ofAddressP($param{name})"; - } elsif ($rawtype eq "MemoryAddress") { - print $dst "$type.create($param{name})"; - } elsif ($rawtype eq "MemorySegment") { - print $dst "$type.create($param{name}.baseAddress())"; - } else { - print $dst "$param{name}"; - } - print $dst ", " if ($pi != $params[$#params]); - } - print $dst ")"; - if ($rawresult eq "MemoryAddress") { - print $dst ".addr()"; - } elsif ($rawresult eq "MemorySegment") { - print $dst ".addr().segment()"; - } - print $dst ";\n"; - - print $dst "\t\t};\n"; - print $dst "\t\treturn Native.Pointer.ofCallback(MethodHandles.lookup(), v, func, \"$signature\");\n"; - print $dst "\t}\n"; - - print $dst "}\n"; - - if (!$enclosingType) { - close($dst); - } - } -} - -# Finish off -if ($enclosingType) { - print $dst "}\n"; - close($dst); -} diff --git a/src/generate-native b/src/generate-native new file mode 100755 index 0000000..55eaee7 --- /dev/null +++ b/src/generate-native @@ -0,0 +1,893 @@ +#!/usr/bin/perl + +# -*- Mode:perl; perl-indent-level:4;tab-width:4; -*- + +# TODO: get/set should take a typed field for struct/union * +# TODO: a flag for func_name to funcName option + +use Data::Dumper; +use File::Basename; +use File::Path qw(make_path); + +$scriptPath = dirname(__FILE__); + +$package = ""; +$output = "bin"; +$verbose = 0; + +# usage +# -t package target package +# -d directory output root +# -v verbose + +my %typeSizes = ( + i8 => 'byte', u8 => 'byte', + i16 => 'short', u16 => 'short', + i32 => 'int', u32 => 'int', + i64 => 'long', u64 => 'long', + f32 => 'float', + f64 => 'double', + ); + +my %intSizes = ( 8 => 'byte', 16 => 'short', 32 => 'int', 64 => 'long' ); +my %typePrimitive = ( + "byte" => 8, + "short" => 16, + "int" => 32, + "long" => 64, + "float" => 32, + "double" => 64, + ); + +my %typeSignature = ( + "byte" => "B", + "short" => "S", + "int" => "I", + "long" => "J", + "float" => "F", + "double" => "D", + "MemorySegment" => "Ljdk/incubator/foreign/MemorySegment;", + "MemoryAddress" => "Ljdk/incubator/foreign/MemoryAddress;", + ); + +while (@ARGV) { + my $cmd = shift(@ARGV); + + if ($cmd eq "-t") { + $package = shift(@ARGV); + } elsif ($cmd eq "-d") { + $output = shift(@ARGV); + } elsif ($cmd eq "-v") { + $verbose++; + } else { + $meta = $cmd; + } +} + +# load in interface file +do $meta; + +analyseAndFixTypes(); + +if ($verbose) { + print "Using:n"; + print Dumper(\%data); +} + +# anonymous structs +# the exporter doesn't output anonymous structs as they might +# just be forward references. this fills in any missing types. +# anonymouse calls +# anonymous functions are referenced by signature, convert any to an identifier +# typeInfo +# setup typeInfo for all type references - memebers, fields, return values +sub analyseAndFixTypes { + my @replace = (); + + # pass 1, fix call definition names and keys + foreach $old (keys %data) { + if ($old =~ m/^call:/) { + push @replace, $old; + } + } + foreach $old (@replace) { + my $new = $old; + my $c; + + $new =~ s/(.*)\((.*)\)(.*)/$1Call_$2_$3/; + $data{$new} = $c = delete $data{$old}; + $c->{name} =~ s/(.*)\((.*)\)(.*)/$1Call_$2_$3/; + } + + # pass 2 add typeinfo and anonymous types, fix call types + foreach $n (keys %data) { + my $s = $data{$n}; + my @list; + + + if ($s->{type} =~ m/struct|union/) { + @list = @{$s->{fields}}; + } elsif ($s->{type} =~ m/func|call/) { + @list = @{$s->{arguments}}; + push @list, $s->{result}; + } + + foreach $a (@list) { + if ($a->{type} =~ m/(struct|union):(.*)/ && !defined($data{$a->{type}})) { + print "Add anonymous $1 $2\n"; + $data{$a->{type}} = { + name => $2, + type => $1, + size => 0 + }; + } + + if ($a->{type} =~ m/^call:/) { + $a->{type} =~ s/(.*)\((.*)\)(.*)/$1Call_$2_$3/; + } + + # must be last + $a->{typeInfo} = queryTypeInfo($a); + } + } + + # pass 3 create java signatures + foreach $n (keys %data) { + my $s = $data{$n}; + + if ($s->{type} =~ m/^(call|func)$/) { + $s->{signature} = formatSignature($s); + } + } +} + +sub isVoid { + my $m = shift @_; + + return $m->{type} eq 'void' && !$m->{deref}; +} + +# format a single layout type item for non-bitfield types +# type - type record that contains type and deref +# withName - '.withName()' - empty, or really any other MemoryLayout adjustment functions. +sub formatTypeLayout { + my $m = shift @_; + my $withName = shift @_; + my $desc = ""; + + if ($m->{deref} =~ m/^(u64|u32):/) { + $desc .= "Memory.POINTER$withName"; + } elsif ($m->{type} =~ m/^([iuf]\d+)$/) { + if ($m->{deref} =~ m/\[(\d*).*\]/) { + $desc .= "MemoryLayout.sequenceLayout($1, Memory.".uc($typeSizes{$m->{type}}).")$withName"; + } else { + $desc .= 'Memory.'.uc($typeSizes{$m->{type}})."$withName"; + } + } elsif ($m->{type} =~ m/^(struct|union):(.*)/) { + my $type = $2; + if ($m->{deref} =~ m/\[(\d*).*\]/) { + $desc .= "MemoryLayout.sequenceLayout($1, $type.LAYOUT)$withName"; + } else { + $desc .= "$type.LAYOUT$withName"; + } + } else { + print Dumper($m); + die ("unknown type"); + } + + return $desc; +} + +sub formatFunctionDescriptor { + my $c = shift @_; + my @arguments = @{$c->{arguments}}; + my $result = $c->{result}; + my $desc; + my $index = 0; + + if (!isVoid($result)) { + $desc = "FunctionDescriptor.of(\n "; + $desc .= formatTypeLayout($result); + $index = 1; + } else { + $desc = "FunctionDescriptor.ofVoid(\n "; + } + + foreach $m (@arguments) { + $desc .= ",\n " if ($index++ > 0); + $desc .= formatTypeLayout($m, ".withName(\"$m->{name}\")"); + } + + $desc .= "\n)"; + + return $desc; +} + +sub formatSignature { + my $c = shift @_; + my @arguments = @{$c->{arguments}}; + my $desc = '('; + + foreach $m (@arguments) { + $desc .= $typeSignature{$m->{typeInfo}->{carrier}}; + } + $desc .= ')'; + + if ($c->{result}->{typeInfo}->{type} ne 'void') { + $desc .= $typeSignature{$c->{result}->{typeInfo}->{carrier}}; + } else { + $desc .= 'V'; + } + + return $desc; +} + +sub queryTypeInfo { + my $m = shift @_; + my $info = {}; + + # default for everything not specifically handled + $info->{carrier} = "MemoryAddress"; + $info->{resolve} = "(Addressable)Memory.address(\${value})"; + + if ($m->{deref} =~ m/^(u64:|u32:)\(/) { + # This is a function pointer, type must be type = 'call:.*' + if ($m->{type} =~ m/^call:(.*)/) { + $info->{type} = "Memory.FunctionPointer<$1>"; + $info->{create} = "$1.downcall(\${result}, scope())"; + } else { + die(); + } + } elsif ($m->{type} =~ m/^([iuf]\d+)$/) { + if ($m->{deref} =~ m/\[(\d*).*\]/) { + $info->{byValue} = 1; + $info->{type} = "Memory.".ucfirst($typeSizes{$m->{type}})."Array"; + $info->{create} = $info->{type}.".create(\${result})"; + } elsif ($m->{deref} =~ m/^(u64:u64:|u32:u32:)/) { + $info->{type} = "Memory.PointerArray"; + $info->{create} = $info->{type}.".create(\${result})"; + } elsif ($m->{deref} =~ m/^(u64:|u32:)/) { + $info->{type} = "Memory.".ucfirst($typeSizes{$m->{type}})."Array"; + $info->{create} = $info->{type}.".create(\${result})"; + } else { + $info->{type} = $typeSizes{$m->{type}}; + $info->{carrier} = $typeSizes{$m->{type}}; + $info->{resolve} = "($info->{type})(\${value})"; + $info->{create} = "\${result}"; + } + } elsif ($m->{type} =~ m/^(struct|union):(.*)/) { + my $type = $2; + if ($m->{deref} =~ m/\[(\d*).*\]/) { + $info->{type} = $type; + $info->{create} = $info->{type}.".createArray($1, \${result}, scope())"; + } elsif ($m->{deref} =~ m/^(u64:u64:|u32:u32:)/) { + $info->{type} = "Memory.PointerArray"; + $info->{create} = $info->{type}.".create(\${result})"; + } elsif ($m->{deref} =~ m/^(u64:|u32:)/) { + $info->{type} = $type; + $info->{create} = $info->{type}.".create(\${result}, scope())"; + } else { + $info->{type} = $type; + $info->{create} = $info->{type}.".create(\${result})"; + } + } elsif ($m->{type} eq "void") { + if ($m->{deref} =~ m/^(u64:u64:|u32:u32:)/) { + $info->{type} = "Memory.PointerArray"; + $info->{create} = $info->{type}.".create(\${result})"; + } elsif ($m->{deref} =~ m/^(u64:|u32:)/) { + $info->{type} = "MemoryAddress"; + $info->{create} = "\${result}"; + $info->{resolve} = "(Addressable)\${value}"; + } else { + $info->{type} = "void"; + $info->{carrier} = "void"; + delete $info->{resolve}; + } + } else { + print Dumper($m); + die ("unknown type"); + } + + return $info; +} + +sub formatFunction { + my $c = shift @_; + my @arguments = @{$c->{arguments}}; + my $result = $c->{result}; + my $desc; + my $index = 0; + my $desc; + my $name = $c->{name}; + + my $rtype = $result->{typeInfo}->{type}; + + #print Dumper($c); + + $desc = $rtype; + $desc .= " $name("; + + for $m (@arguments) { + $desc .= ", " if ($index++ > 0); + $desc .= $m->{typeInfo}->{type}; + $desc .= " $m->{name}" + } + $desc .=") {\n "; + $index = 0; + + $desc .= "try {\n"; + $desc .= " $result->{typeInfo}->{carrier} res\$value = ($result->{typeInfo}->{carrier})" if ($rtype ne "void"); + $desc .= " " if ($rtype eq "void"); + + $desc .= "$name\$FH.invokeExact(\n "; + for $m (@arguments) { + my $resolve = $m->{typeInfo}->{resolve}; + + $desc .= ",\n " if ($index++ > 0); + + if ($resolve) { + $resolve =~ s/\$\{value\}/$m->{name}/g; + $desc .= $resolve; + } else { + $desc .= "$m->{name}" + } + } + $desc .= ");\n"; + + if ($rtype ne "void") { + my $create = $result->{typeInfo}->{create}; + + # ooh, templates could insert other arguments or values as well? + $create =~ s/\${result\}/res\$value/; + + $desc .= " return $create;\n"; + } + # throw Error()? + $desc .= " } catch (Throwable t) { throw new RuntimeException(t); }\n"; + + $desc .="}"; + + return $desc; +} + +# create an interface for function pointers +# FiXME: this should be exportCallback to a file +sub formatCallback { + my $c = shift @_; + my @arguments = @{$c->{arguments}}; + my $result = $c->{result}; + my $desc; + my $index = 0; + my $desc; + my $name = $c->{name}; + + #print "\nCall\n"; + #print Dumper($c); + + my $rtype = $result->{typeInfo}->{type}; + + $desc = "\@FunctionalInterface\n"; + $desc .= "public interface $name {\n"; + + # the public (functional) interface + $index = 0; + $desc .= " $result->{typeInfo}->{type} call("; + for $m (@arguments) { + $desc .= ", " if ($index++ > 0); + $desc .= $m->{typeInfo}->{type}; + $desc .= " $m->{name}" + } + $desc .= ");\n"; + + # the internal interface + $index = 0; + $desc .= " \@FunctionalInterface\n"; + $desc .= " interface Trampoline {\n "; + $desc .= $result->{typeInfo}->{carrier}; + $desc .= " call("; + for $m (@arguments) { + $desc .= ", " if ($index++ > 0); + $desc .= $m->{typeInfo}->{carrier}; + $desc .= " $m->{name}" + } + $desc .= ");\n"; + $desc .= " }\n\n"; + + # native descriptor + $desc .= " static FunctionDescriptor DESCRIPTOR() {\n"; + $desc .= " return "; + my $tmp = formatFunctionDescriptor($c); + $tmp =~ s/^/ /mg; + $tmp =~ s/^ *//; + $desc .= $tmp; + $desc .= ";\n }\n"; + + # Factory method for upcalls + # TODO: optional? + $desc .= " public static Memory.FunctionPointer<$name> upcall($name target, ResourceScope scope) {\n"; + $desc .= " Trampoline trampoline = ("; + $index = 0; + for $m (@arguments) { + $desc .= ", " if ($index++ > 0); + $desc .= "$m->{name}" + } + $desc .= ") -> {\n"; + #$desc .= " try {\n"; + $desc .= " "; + $desc .= "return " if $rtype ne "void"; + $desc .= "target.call(\n "; + $index = 0; + for $m (@arguments) { + my $create = $m->{typeInfo}->{create}; + + $create =~ s/\$\{result\}/$m->{name}/g; + + $desc .= ",\n " if ($index++ > 0); + $desc .= "$create"; + } + $desc .= ");\n"; + #$desc .= " } catch (Exception x) { }{\n"; + # FIXME: or null for address + #$desc .= " return 0;\n" if $rtype != "void"; + #$desc .= " }\n"; + $desc .= " };\n"; + + $desc .= " return new Memory.FunctionPointer<>(\n"; + $desc .= " Memory.upcall(\n"; + $desc .= " trampoline,\n"; + $desc .= " \"call\",\n"; + $desc .= " \"$c->{signature}\",\n"; + $desc .= " DESCRIPTOR(),\n"; + $desc .= " scope),\n"; + $desc .= " target);\n"; + $desc .= " }\n"; + + # downcalls + $desc .= " public static Memory.FunctionPointer<$name> downcall(MemoryAddress addr, ResourceScope scope) {\n"; + $desc .= " NativeSymbol symbol = NativeSymbol.ofAddress(\"$name\", addr, scope);\n"; + $desc .= " MethodHandle $name\$FH = Memory.downcall(symbol, DESCRIPTOR());\n"; + $desc .= " return new Memory.FunctionPointer<$name>(\n"; + $desc .= " symbol,\n"; + + # HACK: this is basically the same as any function call, just patch in the changes for now + $tmp = formatFunction($c); + + $tmp =~ s/^(.*) ($name)\(/(/; + $tmp =~ s/\) \{/) -> {/; + $tmp =~ s/^/ /mg; + $desc .= $tmp; + $desc .= "\n"; + $desc .= " );\n"; + $desc .= " }\n"; + $desc .= "}\n"; + + # replace leading ' ' with '\t' + $desc =~ s/(?:\G|^) /\t/mg; + + return $desc; +} + +# some bitfield support stuff. +# maximum size allowed for field holder based on start offset +# offset +sub fieldMaxHolder { + my $offset = shift @_; + + return 64 if ($offset & 63) == 0; + return 32 if ($offset & 31) == 0; + return 16 if ($offset & 15) == 0; + return 8 if ($offset & 7) == 0; + return 0; +} + +sub fieldLimit { + my $size = shift @_; + + return 64 if ($size > 32); + return 32 if ($size > 16); + return 16 if ($size > 8); + return 8; +} + +# offset, size +# returns @sizes required to hold them, based on alignment rules +sub fieldHolders { + my $offset = shift @_; + my $bits = shift @_; + my $end = $offset + $bits; + my @sizes = (); + + while ($offset < $end) { + my $limit = fieldLimit($bits); + my $max = fieldMaxHolder($offset); + my $step = ($limit < $max) ? $limit : $max; + + push @sizes, $step; + + $offset += $step; + $bits -= $step; + } + + return @sizes; +} + +sub formatLayout { + my $s = shift @_; + my @fields = @{$s->{fields}}; + my $index = 0; + my $bitfieldIndex = 0; + my $desc; + my $last = 0; + my $maxSize = 8; + + $desc = "MemoryLayout.$s->{type}Layout(\n "; + + for (my $i = 0; $i <= $#fields; $i++) { + my $f = $fields[$i]; + + if ($f->{offset} > $last) { + $desc .= ",\n" if ($index++ > 0); + $desc .= ' MemoryLayout.paddingLayout('.($f->{offset} - $last).')'; + } + + $maxSize = fieldLimit($f->{size}) if (fieldLimit($f->{size}) > $maxSize); + + if ($f->{ctype} eq 'bitfield') { + my $start = $f->{offset}; + my $end = $f->{size} + $f->{offset}; + my $j = $i + 1; + my $max = fieldMaxHolder($start); + + # breaks bitfields into char/short/int/long blocks + # TODO: need more info for mapping to get/settters + + #print "> $f->{name} $f->{size} @ $f->{offset}\n"; + + while ($j <= $#fields && $fields[$j]->{ctype} eq "bitfield") { + my $g = $fields[$j]; + + #print "> $g->{name} $g->{size} @ $g->{offset}\n"; + + if ($g->{offset} > $end || ($g->{offset} - $start >= $max)) { + foreach $size (fieldHolders($start, $end - $start)) { + $desc .= ",\n " if ($index++ > 0); + $desc .= 'Memory.'.uc($intSizes{$size}).".withName(\"bitfield\$$bitfieldIndex\")"; + $bitfieldIndex++; + } + $desc .= ",\n " if ($index++ > 0); + $desc .= 'MemoryLayout.paddingLayout('.($g->{offset}-$end).')'; + $start = $g->{offset}; + $max = fieldMaxHolder($start); + } + $end = $g->{size} + $g->{offset}; + $j++; + } + + foreach $size (fieldHolders($start, $end - $start)) { + $desc .= ",\n " if ($index++ > 0); + $desc .= 'Memory.'.uc($intSizes{$size}).".withName(\"bitfield\$$bitfieldIndex\")"; + $bitfieldIndex++; + } + + + $i = $j-1; + } else { + $desc .= ",\n " if ($index++ > 0); + $desc .= formatTypeLayout($f, ".withName(\"$f->{name}\")"); + } + + $last = $fields[$i]->{offset} + $fields[$i]->{size}; + } + + if ($last < $s->{size}) { + $desc .= ",\n " if ($index++ > 0); + $desc .= 'MemoryLayout.paddingLayout('.($s->{size} - ${last}).')'; + } + + $desc .= "\n)"; + $desc .= ".withBitAlignment($maxSize)"; + + return $desc; +} + +sub formatGetSet { + my $s = shift @_; + my $m = shift @_; + my $desc = ""; + my $info = $m->{typeInfo}; + my $Name = ucfirst($m->{name}); + my $tmp; + + # info -> needsalloc? + + # TODO: String + # TODO: embedded arrays are quite different setup + + if ($info->{byValue}) { + $tmp = $info->{create}; + $tmp =~ s/\$\{result\}/segment/g; + + $desc .= " public $info->{type} get$Name() {\n"; + $desc .= " MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + $desc .= " MemorySegment segment = this.segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize());\n"; + $desc .= " return $tmp;\n"; + $desc .= " }\n"; + + $desc .= " public $info->{type} get$Name"."At(long index) {\n"; + $desc .= " MemorySegment segment = this.segment.asSlice(LAYOUT.byteSize() * index, LAYOUT.byteSize());\n"; + $desc .= " MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + $desc .= " segment = this.segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize());\n"; + $desc .= " return $tmp;\n"; + $desc .= " }\n"; + } else { + $tmp = $info->{create}; + $tmp =~ s/\$\{result\}/($info->{carrier})$m->{name}\$VH.get(segment)/g; + + $desc .= " public $info->{type} get$Name() {\n"; + $desc .= " return $tmp;\n"; + $desc .= " }\n"; + + $desc .= " public $info->{type} get$Name"."At(long index) {\n"; + $desc .= " MemorySegment segment = this.segment.asSlice(LAYOUT.byteSize() * index, LAYOUT.byteSize());\n"; + $desc .= " return $tmp;\n"; + $desc .= " }\n"; + } + + if (!($m->{deref} =~ m/\[(\d*).*\]/)) { + $tmp = $info->{resolve}; + $tmp =~ s/\$\{value\}/value/g; + + $desc .= " public void set$Name($info->{type} value) {\n"; + $desc .= " $m->{name}\$VH.set(segment, $tmp);\n"; + $desc .= " }\n"; + + $desc .= " public void set$Name"."At(long index, $info->{type} value) {\n"; + $desc .= " MemorySegment segment = this.segment.asSlice(LAYOUT.byteSize() * index, LAYOUT.byteSize());\n"; + $desc .= " $m->{name}\$VH.set(segment, $tmp);\n"; + $desc .= " }\n"; + } + + # indexed + + return $desc; +} + +sub exportStruct { + my $f = shift @_; + my $s = shift @_; + my @fields = @{$s->{fields}}; + my $isHandle = $s->{size} == 0; + #my @functions = @{shift @_}; + + print $f "package $package;\n" if $package; + + print $f "import jdk.incubator.foreign.*;\n"; + print $f "import java.lang.invoke.*;\n"; + + print $f "public class $s->{name} implements Memory.Addressable {\n"; + + # TODO: parameterise and use typeInfo data. + if (!$isHandle) { + print $f " MemorySegment segment;\n"; + # constructors + print $f " private $s->{name}(MemorySegment segment) { this.segment = segment; }\n"; + print $f " public static $s->{name} create(MemorySegment segment) { return new $s->{name}(segment); }\n"; + print $f " public static $s->{name} create(MemoryAddress address, ResourceScope scope) {\n"; + print $f " return create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope));\n"; + print $f " }\n"; + print $f " public static $s->{name} createArray(MemoryAddress address, long size, ResourceScope scope) {\n"; + print $f " return create(MemorySegment.ofAddress(address, size * LAYOUT.byteSize(), scope));\n"; + print $f " }\n"; + print $f " public static $s->{name} create(Frame frame) { return create(frame.allocate(LAYOUT)); }\n"; + print $f " public static $s->{name} create(ResourceScope scope) { return create(MemorySegment.allocateNative(LAYOUT, scope)); }\n"; + print $f " public MemoryAddress address() { return segment.address(); }\n"; + print $f " public ResourceScope scope() { return segment.scope(); }\n"; + } else { + # not sure if handles need scopes + print $f " MemoryAddress address;\n"; + print $f " ResourceScope scope;\n"; + # constructors + print $f " private $s->{name}(MemoryAddress address, ResourceScope scope) { this.address = address; this.scope = scope}\n"; + print $f " public static $s->{name} create(MemoryAddress address) { return new $s->{name}(address); }\n"; + print $f " public MemoryAddress address() { return address; }\n"; + print $f " public ResourceScope scope() { return scope; }\n"; + } + + # FIXME: use typeInfo + # TODO: indexed accessors + # accessors + if (1) { + foreach $m (@fields) { + print $f formatGetSet($s, $m); + } + } else { + foreach $m (@fields) { + my $Name = ucfirst($m->{name}); + + print $f " // [$m->{deref}] [$m->{type}] [$m->{ctype}]\n"; + + if ($m->{deref} =~ m/^(u64:|u32:)\(/) { + # This is a function pointer, type must be type = 'call:.*' + + if ($m->{type} =~ m/^call:(.*)/) { + my $jtype = $1; + + $jtype =~ s/(.*)\((.*)\)(.*)/Call$1_$2_$3/; + + print $f " public Memory.FunctionPointer<$jtype> get$Name() {\n"; + print $f " // FIXME: better scope\n"; + print $f " return $jtype.downcall((MemoryAddress)$m->{name}\$VH.get(segment), scope());\n"; + print $f " }\n"; + + print $f " public void set$Name(Memory.FunctionPointer<$jtype> value) {\n"; + print $f " $m->{name}\$VH.set(segment, Memory.address(value));\n"; + print $f " }\n"; + } + } elsif ($m->{deref} =~ m/^(u64:|u32:)/) { + # all other pointer types require extra context, e.g. a length or type + print $f " public MemoryAddress get$Name() {\n"; + print $f " return (MemoryAddress)$m->{name}\$VH.get(segment);\n"; + print $f " }\n"; + + # FIXME: set could use the type though + print $f " public void set$Name(MemoryAddress value) {\n"; + print $f " $m->{name}\$VH.set(segment, Memory.address(value));\n"; + print $f " }\n"; + } elsif ($m->{type} eq "bitfield") { + # TODO + } elsif ($m->{type} =~ m/^struct|union:(.*)$/) { + my $jtype = $1; + + $jtype = "Memory.HandleArray<$jtype>" if ($data{$m->{type}}->{size} == 0); + + # embedded type including arrays + print $f " public $jtype get$Name() {\n"; + print $f " MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + print $f " MemorySegment seg = segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize());\n"; + print $f " return $jtype.create(seg, $jtype::new);\n" if ($data{$m->{type}}->{size} == 0); + print $f " return $jtype.create(seg);\n" if ($data{$m->{type}}->{size} != 0); + print $f " }\n"; + } elsif ($m->{type} =~ m/^[uif]\d+$/) { + my $jtype = $typeSizes{$m->{type}}; + my $Jtype = ucfirst($jtype); + my $JTYPE = uc($jtype); + + if ($m->{deref} =~ m/\[(\d*).*\]/) { + # array type + print $f " public Memory.$Jtype"."Array get$Name() {\n"; + print $f " MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + print $f " MemorySegment seg = segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize());\n"; + print $f " return new Memory.$Jtype"."Array(seg);\n"; + print $f " }\n"; + } else { + # primitive type + print $f " public $jtype get$Name() {\n"; + print $f " return ($jtype)$m->{name}\$VH.get(segment);\n"; + print $f " }\n"; + + print $f " public void set$Name($jtype value) {\n"; + print $f " $m->{name}\$VH.set(segment, ($jtype)value);\n"; + print $f " }\n"; + } + } + # struct print $f " MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + + } + } + + # layout and varhandles + if ($#fields >= 0) { + print $f "static final GroupLayout LAYOUT = ".formatLayout($s).";\n"; + + foreach $m (@fields) { + next if ($m->{typeInfo}->{byValue}); + print $f " static final VarHandle $m->{name}\$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n"; + } + } + + print $f "}\n"; +} + +# copies a skeleton file and patches it to the target package +sub copySkeletonFile { + my $src = shift @_; + my $dst = shift @_; + + 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 $package;/; + print $d $_; + } + + close $s; + close $d; + +} + +# init output +$outputPath = $package; +$outputPath =~ s@\.@/@g; +$outputPath = "$output/$outputPath"; + +make_path($outputPath); + +copySkeletonFile("$scriptPath/template/Memory.java", "$outputPath/Memory.java"); +copySkeletonFile("$scriptPath/template/Frame.java", "$outputPath/Frame.java"); + +sub nameToPath { + my $dir = shift @_; + my $name = shift @_; + + $name =~ s@\.@/@g; + $name = "$dir/$name.java"; + return $name; +} + +foreach $x (grep { m/^(struct|union):/ } sort keys %data) { + my $s = $data{$x}; + my $path = nameToPath($output, "$package.$s->{name}"); + + open (my $f, ">", $path) || die ("Cannot open '$path' for writing"); + + exportStruct($f, $s); + + close $f; +} + +foreach $x (grep { m/^call:/ } sort keys %data) { + my $c = $data{$x}; + my $name = $c->{name}; + + my $path = nameToPath($output, "$package.$name"); + + open (my $f, ">", $path) || die ("Cannot open '$path' for writing"); + + print $f "package $package;\n"; + print $f "import jdk.incubator.foreign.*;\n"; + print $f "import java.lang.invoke.*;\n"; + + print $f formatCallback($c); + + close $f; +} + +# just quick and dirty for now +# may want a non-static version with a specific scope? +{ + my @functions = grep { /^func:/ } keys %data; + my $lib = { + name => "APILib", + functions => \@functions + }; + + + my $path = nameToPath($output, "$package.$lib->{name}"); + + open (my $f, ">", $path) || die ("Cannot open '$path' for writing"); + + print $f "package $package;\n"; + print $f "import jdk.incubator.foreign.*;\n"; + print $f "import java.lang.invoke.*;\n"; + + print $f "public class $lib->{name} {\n"; + + print $f " static ResourceScope scope() { return ResourceScope.globalScope(); }\n"; + foreach $cname (@{$lib->{functions}}) { + my $c = $data{$cname}; + my $tmp; + + print $f " static final MethodHandle $c->{name}\$FH = Memory.downcall(\"$c->{name}\",\n"; + $tmp = formatFunctionDescriptor($c); + print $f "$tmp);\n"; + + $tmp = formatFunction($c); + print $f "public static "; + print $f $tmp; + } + + print $f "}\n"; + + close $f; +} diff --git a/src/template/Frame.java b/src/template/Frame.java new file mode 100644 index 0000000..5eafe57 --- /dev/null +++ b/src/template/Frame.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package api; + +import jdk.incubator.foreign.*; +import static jdk.incubator.foreign.ValueLayout.OfAddress; + +public interface Frame extends AutoCloseable, SegmentAllocator { + + @Override + MemorySegment allocate(long size, long alignment); + + @Override + public void close(); + + default MemorySegment allocateInt() { + return allocate(Memory.INT); + } + + default MemorySegment allocateInt(int count) { + return allocate(Memory.INT, count); + } + + default MemorySegment allocateLong() { + return allocate(Memory.LONG); + } + + default MemorySegment allocateLong(int count) { + return allocateArray(Memory.LONG, count); + } + + default MemorySegment allocatePointer() { + return allocate(Memory.POINTER); + } + + default MemorySegment allocatePointer(int count) { + return allocateArray(Memory.POINTER, count); + } + + default MemorySegment allocateArray(OfAddress type, MemoryAddress[] value) { + MemorySegment m = allocateArray(type, value.length); + for (int i=0;i MemorySegment copy(T[] array) { + MemorySegment mem = allocateAddress(array.length); + for (int i = 0; i < array.length; i++) + MemoryAccess.setAddressAtIndex(mem, i, array[i].address()); + return mem; + } + + default MemorySegment copy(T value) { + return copy(value.address()); + } + + default MemorySegment copy(MemoryAddress value) { + MemorySegment mem = allocateAddress(); + MemoryAccess.setAddress(mem, value); + return mem; + } + */ + // create an array pointing to strings + default MemorySegment copy(String[] array) { + if (array != null) { + MemorySegment list = allocatePointer(array.length); + for (int i = 0; i < array.length; i++) { + list.setAtIndex(Memory.POINTER, i, copy(array[i])); + } + return list; + } else { + return Memory.NULL; + } + } + +} diff --git a/src/template/Memory.java b/src/template/Memory.java new file mode 100644 index 0000000..1cdf9f6 --- /dev/null +++ b/src/template/Memory.java @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2020 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package api; + +import java.lang.invoke.*; +import java.lang.ref.Cleaner; +import jdk.incubator.foreign.*; +import static jdk.incubator.foreign.ValueLayout.*; + +import java.util.AbstractList; +import java.util.function.Function; +import java.util.function.BiFunction; + +/** + * A utility for memory operations including a stack allocator. + *

+ * The stack allocator works like this + *

+ * try (Frame f = Memory.createFrame()) {
+ *		MemorySegment a = f.allocate(size);
+ * }
+ * 
+ * Any memory allocated is freed when the frame is closed. + *

+ * This is MUCH faster than using MemorySegment.allocateNative(). + */ +public class Memory { + + // probably should be INT8 INT16, etc + public static final OfByte BYTE = JAVA_BYTE; + public static final OfShort SHORT = JAVA_SHORT.withBitAlignment(16); + public static final OfInt INT = JAVA_INT.withBitAlignment(32); + public static final OfLong LONG = JAVA_LONG.withBitAlignment(64); + public static final OfFloat FLOAT = JAVA_FLOAT.withBitAlignment(32); + public static final OfDouble DOUBLE = JAVA_DOUBLE.withBitAlignment(64); + public static final OfAddress POINTER = ADDRESS.withBitAlignment(64); + + static final ResourceScope sharedScope = ResourceScope.newSharedScope(); // cleaner? + static final MemorySegment NULL = MemorySegment.ofAddress(MemoryAddress.NULL, 1, ResourceScope.globalScope()); + + public static ResourceScope sharedScope() { + return sharedScope; + } + + public static MethodHandle downcall(String name, FunctionDescriptor desc) { + return SymbolLookup.loaderLookup().lookup(name) + .map(sym -> CLinker.systemCLinker().downcallHandle(sym, desc)) + .orElse(null); + } + + public static MethodHandle downcall(NativeSymbol sym, FunctionDescriptor desc) { + return CLinker.systemCLinker().downcallHandle(sym, desc); + } + + public static MethodHandle downcall(String name, MemoryAddress sym, FunctionDescriptor desc, ResourceScope scope) { + return sym != MemoryAddress.NULL + ? CLinker.systemCLinker().downcallHandle(NativeSymbol.ofAddress(name, sym, scope), desc) + : null; + } + + static final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + public static NativeSymbol upcall(Object instance, FunctionDescriptor desc, ResourceScope scope) { + try { + java.lang.reflect.Method m = instance.getClass().getMethods()[0]; + MethodHandle handle = lookup.findVirtual(instance.getClass(), "call", MethodType.methodType(m.getReturnType(), m.getParameterTypes())) + .bindTo(instance); + return CLinker.systemCLinker().upcallStub(handle, desc, scope); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + + public static NativeSymbol upcall(Object instance, String method, String signature, FunctionDescriptor desc, ResourceScope scope) { + try { + MethodHandle handle = lookup.findVirtual(instance.getClass(), method, MethodType.fromMethodDescriptorString(signature, Memory.class.getClassLoader())) + .bindTo(instance); + return CLinker.systemCLinker().upcallStub(handle, desc, scope); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + static final ResourceScope scope = ResourceScope.newSharedScope(Cleaner.create()); + private static final ThreadLocal stacks = ThreadLocal.withInitial(() -> new Stack(scope)); + + public static Frame createFrame() { + return stacks.get().createFrame(); + } + + static class Stack { + + private final MemorySegment stack; + private long sp; + private Thread thread = Thread.currentThread(); + + Stack(ResourceScope scope) { + stack = MemorySegment.allocateNative(4096, 4096, scope); + sp = 4096; + } + + Frame createFrame() { + + return new Frame() { + private final long tos = sp; + private Thread self = thread; + private ResourceScope scope; + + @Override + public MemorySegment allocate(long size, long alignment) { + if (self != Thread.currentThread()) + throw new IllegalStateException(); + if (alignment != Long.highestOneBit(alignment)) + throw new IllegalArgumentException(); + if (sp >= size) { + sp = (sp - size) & ~(alignment - 1); + return stack.asSlice(sp, size).fill((byte)0); + } else { + if (scope == null) + scope = ResourceScope.newConfinedScope(); + return MemorySegment.allocateNative(size, alignment, scope); + } + } + + @Override + public void close() { + sp = tos; + self = null; + if (scope != null) { + scope.close(); + scope = null; + } + } + }; + } + } + + public interface Addressable { + MemoryAddress address(); + ResourceScope scope(); + } + + public record FunctionPointer(NativeSymbol symbol, T function) { + } + + public static MemoryAddress address(jdk.incubator.foreign.Addressable v) { + return v != null ? v.address() : MemoryAddress.NULL; + } + + public static MemoryAddress address(Memory.Addressable v) { + return v != null ? v.address() : MemoryAddress.NULL; + } + + public static MemoryAddress address(FunctionPointer v) { + return v != null ? v.symbol().address() : MemoryAddress.NULL; + } + + // hmm do i want this or not? + // -> added 'type safety' + // -> load of crap to be written + public static class ByteArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + private ByteArray(MemorySegment segment) { + this.segment = segment; + } + + public static ByteArray create(MemorySegment segment) { + return new ByteArray(segment); + } + + public static ByteArray createArray(MemoryAddress address, long length, ResourceScope scope) { + return create(MemorySegment.ofAddress(address, length, scope)); + } + + public static ByteArray createArray(long length, SegmentAllocator alloc) { + return create(alloc.allocateArray(Memory.BYTE, length)); + } + + public static ByteArray create(String value, SegmentAllocator alloc) { + return create(alloc.allocateUtf8String(value)); + } + + public static ByteArray create(String value, ResourceScope scope) { + return create(SegmentAllocator.nativeAllocator(scope).allocateUtf8String(value)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Byte get(int index) { + return getAtIndex(index); + } + + @Override + public Byte set(int index, Byte value) { + byte old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.BYTE.byteSize(); + } + + public byte getAtIndex(long index) { + return (byte)segment.get(Memory.BYTE, index); + } + + public void setAtIndex(long index, byte value) { + segment.set(Memory.BYTE, index, value); + } + } + + public static class ShortArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public ShortArray(MemorySegment segment) { + this.segment = segment; + } + + public ShortArray(Frame frame, long size) { + this(frame.allocateArray(Memory.SHORT, size)); + } + + public ShortArray(Frame frame, short... values) { + this(frame.allocateArray(Memory.SHORT, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Short get(int index) { + return getAtIndex(index); + } + + @Override + public Short set(int index, Short value) { + short old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.SHORT.byteSize(); + } + + public short getAtIndex(long index) { + return segment.getAtIndex(Memory.SHORT, index); + } + + public void setAtIndex(long index, short value) { + segment.setAtIndex(Memory.SHORT, index, value); + } + } + + public static class IntArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public IntArray(MemorySegment segment) { + this.segment = segment; + } + + public IntArray(Frame frame, long size) { + this(frame.allocateArray(Memory.INT, size)); + } + + public IntArray(Frame frame, int... values) { + this(frame.allocateArray(Memory.INT, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Integer get(int index) { + return getAtIndex(index); + } + + @Override + public Integer set(int index, Integer value) { + int old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.INT.byteSize(); + } + + public int getAtIndex(long index) { + return segment.getAtIndex(Memory.INT, index); + } + + public void setAtIndex(long index, int value) { + segment.setAtIndex(Memory.INT, index, value); + } + } + + public static class LongArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public LongArray(MemorySegment segment) { + this.segment = segment; + } + + public LongArray(Frame frame, long size) { + this(frame.allocateArray(Memory.LONG, size)); + } + + public LongArray(Frame frame, long... values) { + this(frame.allocateArray(Memory.LONG, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Long get(int index) { + return getAtIndex(index); + } + + @Override + public Long set(int index, Long value) { + long old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.LONG.byteSize(); + } + + public long getAtIndex(long index) { + return segment.getAtIndex(Memory.LONG, index); + } + + public void setAtIndex(long index, long value) { + segment.setAtIndex(Memory.LONG, index, value); + } + } + + public static class FloatArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public FloatArray(MemorySegment segment) { + this.segment = segment; + } + + public FloatArray(Frame frame, long size) { + this(frame.allocateArray(Memory.FLOAT, size)); + } + + public FloatArray(Frame frame, float... values) { + this(frame.allocateArray(Memory.FLOAT, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Float get(int index) { + return getAtIndex(index); + } + + @Override + public Float set(int index, Float value) { + float old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.FLOAT.byteSize(); + } + + public float getAtIndex(long index) { + return segment.getAtIndex(Memory.FLOAT, index); + } + + public void setAtIndex(long index, float value) { + segment.setAtIndex(Memory.FLOAT, index, value); + } + } + + public static class DoubleArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public DoubleArray(MemorySegment segment) { + this.segment = segment; + } + + public DoubleArray(Frame frame, long size) { + this(frame.allocateArray(Memory.DOUBLE, size)); + } + + public DoubleArray(Frame frame, double... values) { + this(frame.allocateArray(Memory.DOUBLE, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Double get(int index) { + return getAtIndex(index); + } + + @Override + public Double set(int index, Double value) { + double old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.DOUBLE.byteSize(); + } + + public double getAtIndex(long index) { + return segment.getAtIndex(Memory.DOUBLE, index); + } + + public void setAtIndex(long index, double value) { + segment.setAtIndex(Memory.DOUBLE, index, value); + } + } + + public static class PointerArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + private PointerArray(MemorySegment segment) { + this.segment = segment; + } + + public static PointerArray create(MemorySegment segment) { + return new PointerArray(segment); + } + + public static PointerArray createArray(long size, SegmentAllocator alloc) { + return create(alloc.allocateArray(Memory.POINTER, size)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public MemoryAddress get(int index) { + return getAtIndex(index); + } + + @Override + public MemoryAddress set(int index, MemoryAddress value) { + MemoryAddress old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.POINTER.byteSize(); + } + + public MemoryAddress getAtIndex(long index) { + return segment.getAtIndex(Memory.POINTER, index); + } + + public void setAtIndex(long index, MemoryAddress value) { + segment.setAtIndex(Memory.POINTER, index, value); + } + } + + public static class HandleArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + BiFunction create; + + private HandleArray(MemorySegment segment, BiFunction create) { + this.segment = segment; + this.create = create; + } + + public static HandleArray create(MemorySegment segment, BiFunction create) { + return new HandleArray<>(segment, create); + } + + public static HandleArray createArray(long size, SegmentAllocator alloc, BiFunction create) { + return create(alloc.allocateArray(Memory.POINTER, size), create); + } + + @Override + public final MemoryAddress address() { + return segment.address(); + } + + public final ResourceScope scope() { + return segment.scope(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public T get(int index) { + return getAtIndex(index); + } + + @Override + public T set(int index, T value) { + T old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.POINTER.byteSize(); + } + + public T getAtIndex(long index) { + MemoryAddress ptr = segment.getAtIndex(Memory.POINTER, index); + return ptr != null ? create.apply(ptr, scope) : null; + } + + public void setAtIndex(long index, T value) { + segment.setAtIndex(Memory.POINTER, index, value != null ? value.address() : MemoryAddress.NULL); + } + } + +} diff --git a/src/template/Native.java b/src/template/Native.java new file mode 100644 index 0000000..d214edc --- /dev/null +++ b/src/template/Native.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2021 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package api; + +import java.io.StringReader; +import java.lang.invoke.*; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.function.IntFunction; +import jdk.incubator.foreign.*; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.System.Logger.Level; + +/** + * Base class for all native objects. + *

+ * Handles instantiation and provides helper functions for native access. + *

+ * Work in progress. + *

+ * For better safety the 'p' field should be the CHandle, and addr() would + * call get(). Otherwise one must not release ANY object which might ever + * be used again - including any objects returned by the getInfo(). However ... + * it's a trade-off and it's a lot of code to change. + *

+ * FIXME: there are MemorySegment based accessors for primitive types now, use those + */ +public class Native implements Memory.Addressable { + + private final MemoryAddress p; + + private final static boolean dolog = true; + + protected Native(MemoryAddress p) { + this.p = p; + } + + static System.Logger log() { + return System.getLogger("notzed.native"); + } + + public MemoryAddress address() { + return p; + } + + /* ********************************************************************** */ + /* GC handling */ + /* ********************************************************************** */ + /** + * Resource index. + */ + static private final PointerTable map = new PointerTable(); + + /** + * Reference queue for stale objects. + */ + static private final ReferenceQueue references = new ReferenceQueue<>(); + + private static T createInstance(Class jtype, MemoryAddress p) { + cleanerStep(); + try { + Class[] params = {MemoryAddress.class}; + Constructor cc = jtype.getDeclaredConstructor(params); + + cc.setAccessible(true); + + return cc.newInstance(p); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + log().log(Level.ERROR, "createInstance", ex); + throw new RuntimeException(ex); + } + } + + /* + public static T resolve(Class jtype, MemoryAddress p) { + T o; + + //if (dolog) + log().log(Level.DEBUG, () -> String.format(" resolve $%016x %s", p.offset(), jtype.getName())); + + // Instantiation needs to be synchronized for obvious reasons. + synchronized (map) { + CHandle h = (CHandle) map.get(p); + + if (h == null || (o = jtype.cast(h.get())) == null) { + o = createInstance(jtype, p); + h = new CHandle(o, references, p); + map.putAlways(h); + } + } + return o; + }*/ + public static T resolve(MemoryAddress p, Function create) { + T o; + boolean step = false; + + //if (dolog) + // log().log(Level.DEBUG, () -> String.format(" resolv $%016x %s", Memory.toLong(p), create)); + if (p.toRawLongValue() == 0) + return null; + + // Instantiation needs to be synchronized for obvious reasons. + synchronized (map) { + CHandle h = (CHandle)map.get(p); + + String fmt; + + if (h == null || (o = (T)(h.get())) == null) { + o = create.apply(p); + + fmt = h == null ? " create $%016x %s" : " replac $%016x %s"; + + h = new CHandle(o, references, p); + map.put(h); + step = true; + } else { + fmt = " exists $%016x %s"; + } + { + T x = o; + log().log(Level.DEBUG, () -> String.format(fmt, p.toRawLongValue(), x.getClass().getName())); + } + } + + if (step) + cleanerStep(); + + return o; + } + + /* + public static void register(T o) { + T o; + boolean step = false; + + if (dolog) + log().log(Level.DEBUG, () -> String.format(" regist $%016x %s", o.addr().offset(), o.getClass().getName())); + + CHandle h = new CHandle(o, references, o.addr()); + + synchronized (map) { + map.put(h); + step = true; + } + + if (step) + cleanerStep(); + + return o; + }*/ + public void release() { + WeakReference ref; + + synchronized (map) { + ref = map.remove(p); + } + + if (ref != null) { + if (dolog) + log().log(Level.DEBUG, () -> String.format(" force $%016x %s", p.toRawLongValue(), getClass().getName())); + + ref.enqueue(); + } + } + + public static void release(T a) { + if (a != null) + a.release(); + } + + public static void release(Native... list) { + for (Native o: list) + release(o); + } + + static { + Thread cleanup = new Thread(Native::cleaner, "Native cleaner"); + cleanup.setPriority(Thread.MAX_PRIORITY); + cleanup.setDaemon(true); + cleanup.start(); + } + + private static void cleanerStep() { + try { + CHandle stale = (CHandle)references.poll(); + if (stale != null) { + synchronized (map) { + map.remove(stale.p); + } + stale.release(); + } + } catch (Throwable ex) { + } + } + + /** + * Cleaner thread. + *

+ * This polls the reference queue and releases objects via + * their static release method. + */ + private static void cleaner() { + if (dolog) + log().log(Level.DEBUG, "Native finaliser started"); + try { + while (true) { + CHandle stale = (CHandle)references.remove(); + do { + try { + synchronized (map) { + map.remove(stale.p); + } + stale.release(); + } catch (Throwable ex) { + } + stale = (CHandle)references.poll(); + } while (stale != null); + } + } catch (InterruptedException ex) { + } + } + + private static class CHandle extends WeakReference { + + protected MemoryAddress p; + final Class jtype; + CHandle next; + + CHandle(Native referent, ReferenceQueue references, MemoryAddress p) { + super(referent, references); + this.p = p; + this.jtype = referent.getClass(); + } + + void release() { + try { + if (p != null) { + if (dolog) + log().log(Level.DEBUG, () -> String.format(" releas $%016x %s", p.toRawLongValue(), jtype.getName())); + + Method mm = jtype.getDeclaredMethod("release", MemoryAddress.class); + mm.setAccessible(true); + mm.invoke(null, p); + } + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + log().log(Level.ERROR, jtype.getName(), ex); + } finally { + p = null; + } + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof CHandle) && ((CHandle)obj).p == p; + } + + @Override + public int hashCode() { + //return p.hashCode(); + return hashCode(p); + } + + /** + * Simple hashcode for native pointers. + *

+ * This simply strips the bottom 4 bits from the pointer as + * on a 64-bit system the low 3 bits are typically zero and the 4th + * isn't very well distributed. + * + * @param p + * @return + */ + public static final int hashCode(long p) { + return (int)p >>> 4; + } + + /** + * Sigh, memoryaddress has a miserable hashCode(), it's even worse than Long.hashCode() + */ + public static final int hashCode(MemoryAddress p) { + return p.hashCode() >>> 5; + } + } + + public static void debugFlushAll() { + for (int i = 0; i < 3; i++) { + try { + System.gc(); + Thread.sleep(100); + } catch (InterruptedException x) { + } + CHandle stale = (CHandle)references.poll(); + while (stale != null) { + try { + synchronized (map) { + map.remove(stale.p); + } + stale.release(); + } catch (Throwable ex) { + } + stale = (CHandle)references.poll(); + } + } + } + + public static void debugDumpReachable(String title) { + synchronized (map) { + System.out.println(title); + for (CHandle h: map.table) { + while (h != null) { + Native o = h.get(); + System.out.printf(" $%016x: %s %-40s %s\n", + h.p.toRawLongValue(), + o == null ? "dead" : "live", + h.jtype.getName(), + o); + h = h.next; + } + } + } + } + + /** + * Lightweight pointer hashtable. + *

+ * This serves two purposes: + *

    + *
  1. Track and resolve unique objects based on memory address; + *
  2. Hold hard references to the WeakReference as required by the gc system. + *
+ *

+ * CHandle's are chained directly from the index table, the p field + * is used as a key directly, and hash values are not cached. This combines + * to save significant memory per node. + */ + private static class PointerTable { + + int mask = 63; + int size = 0; + CHandle[] table = new CHandle[64]; + + private void resize(int length) { + CHandle[] ntable = new CHandle[length]; + int nmask = length - 1; + + for (int i = 0; i < table.length; i++) { + CHandle h = table[i]; + + while (h != null) { + CHandle n = h.next; + int k = h.hashCode() & nmask; + + h.next = ntable[k]; + ntable[k] = h; + + h = n; + } + } + + table = ntable; + mask = nmask; + } + + public CHandle put(CHandle h) { + CHandle o = remove(h.p); + + putAlways(h); + + return o; + } + + public void putAlways(CHandle h) { + if (size > table.length) + resize(table.length * 2); + + int i = h.hashCode() & mask; + + h.next = table[i]; + table[i] = h; + size += 1; + } + + public CHandle get(MemoryAddress p) { + int i = CHandle.hashCode(p) & mask; + CHandle h = table[i]; + + while (h != null && !h.p.equals(p)) + h = h.next; + return h; + } + + public CHandle remove(MemoryAddress p) { + int i = CHandle.hashCode(p) & mask; + CHandle h = table[i]; + CHandle a = null; + + while (h != null && !h.p.equals(p)) { + a = h; + h = h.next; + } + if (h != null) { + if (a != null) + a.next = h.next; + else + table[i] = h.next; + size -= 1; + } + + return h; + } + } +} diff --git a/test-api/Makefile b/test-api/Makefile index ba83192..a458279 100644 --- a/test-api/Makefile +++ b/test-api/Makefile @@ -1,22 +1,36 @@ CFLAGS=-g -fPIC -JAVA_HOME ?= /home/notzed/src/openjdk-panama-14 +JAVA_HOME?=/opt/jdk-foreign/jvm/openjdk-19-internal JAVAC=$(JAVA_HOME)/bin/javac JAVA=$(JAVA_HOME)/bin/java -all: bin/libapi.so bin/api.classes bin/classes/api/test/TestAPI.class +JAVACFLAGS=--add-modules jdk.incubator.foreign -bin/classes/api/test/TestAPI.class: api/test/TestAPI.java bin/api.classes - $(JAVAC) -cp bin/classes -d bin/classes $< +api_SOURCES := $(wildcard ../src/api/*.java) +api_demo_SOURCES := $(wildcard src/api/test/*.java) -bin/api.classes: bin/api.pm - ../src/generate -d bin/java -t api -c APILib -lapi -s api ./bin/api.pm - $(JAVAC) -d bin/classes bin/java/api/*.java +all:: + mkdir -p bin + +all:: bin/demo.built + +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) + ../src/generate-native -d bin/java -t proto.api ./bin/api.pm touch $@ -bin/api.pm: api.h - gcc -fplugin=../src/export.so -fplugin-arg-export-output=$@ ./$< -o /dev/null +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 $@ $< @@ -24,12 +38,12 @@ bin/api.o: api.c api.h bin/libapi.so: bin/api.o $(CC) -o $@ -shared $^ -check: all - $(JAVA) -Djava.library.path=bin -cp bin/classes api.test.TestAPI +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: check clean - -$(if $(filter clean,$(MAKECMDGOALS)),,$(shell mkdir -p bin)) +.PHONY: demo clean all diff --git a/test-api/api.c b/test-api/api.c index 9dbb2a3..0aad6b8 100644 --- a/test-api/api.c +++ b/test-api/api.c @@ -3,16 +3,6 @@ #include #include "api.h" -void print_data(struct data *data) { - while (data) { - printf("%p: a=%d b=%d c=%d d=%d", data, data->a, data->b, data->c, data->d); - if (data->test_a) - printf(" data->test_a()=%d", data->test_a()); - printf("\n"); - data = data->next; - } -} - static void funca(int a) { printf("funca: %d\n", a); } @@ -28,6 +18,8 @@ static int funcc(float f) { } void *api_func(const char *name) { + printf("requesting function %s\n", name); + if (strcmp(name, "funca") == 0) return funca; else if (strcmp(name, "funcb") == 0) @@ -37,3 +29,23 @@ void *api_func(const char *name) { else return NULL; } + +void print_data(struct data *data) { + while (data) { + printf("%p: a=%d b=%d c=%d d=%d", data, data->a, data->b, data->c, data->d); + if (data->test_a) + printf(" data->test_a()=%d", data->test_a()); + printf("\n"); + data = data->next; + } +} + +struct api *api_create(void) { + struct api *api = malloc(sizeof(*api)); + + api->funca = funca; + api->funcb = funcb; + api->funcc = funcc; + + return api; +} diff --git a/test-api/api.h b/test-api/api.h index 7fc1c3d..e4639ca 100644 --- a/test-api/api.h +++ b/test-api/api.h @@ -4,17 +4,21 @@ struct data { int a; int b; - int c:3; - unsigned d:5; + 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); -void *api_func(const char *name); - 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); diff --git a/test-api/api/test/TestAPI.java b/test-api/api/test/TestAPI.java deleted file mode 100644 index b463b9d..0000000 --- a/test-api/api/test/TestAPI.java +++ /dev/null @@ -1,110 +0,0 @@ - -package api.test; - -import java.foreign.*; -import java.foreign.memory.*; -import java.foreign.annotations.*; -import java.util.function.IntFunction; - -import api.*; - -public class TestAPI { - - @NativeHeader() - public interface APIExt { - - @NativeFunction(value="(i32)v") - public void funca(int a); - - @NativeFunction(value="(i32)i32") - public int funcb(short b); - - @NativeFunction(value="(f32)i32") - public int funcc(float b); - - @NativeFunction(value="(f32)i32") - public int funcd(float b); - - public static boolean exists(APILib lib, String func) { - } - - public static APIExt bind(APILib lib) { - APIExt ext = Libraries.bind(APIExt.class, (String name)->{ - Scope ss = s.fork(); - Pointer cname = ss.allocateCString(name); - Pointer p = lib.api_func(cname); - ss.close(); - - return new Library.Symbol() { - public String getName() { - return name; - } - public Pointer getAddress() { - System.out.printf(" %s -> %016x\n", name, p.addr()); - return p; - } - }; - }); - } - } - - public static void main(String[] args) { - APILib lib = APILib.bind; - LayoutType dataLayout = LayoutType.ofStruct(Data.class); - - try (Scope s = Libraries.libraryScope(lib).fork()) { - Pointer a = s.allocate(dataLayout); - Pointer b = s.allocate(dataLayout); - Callback cb = s.allocateCallback(Call__I.class, - () -> { - return 56; - }); - - Data ad = a.get(); - Data bd = b.get(); - - ad.setNext(b); - ad.setA(1); - ad.setB(2); - ad.setC((byte)3); - ad.setD((byte)4); - - bd.setA(5); - bd.setB(6); - bd.setC((byte)255); - bd.setD((byte)255); - - bd.setTestA(cb); - - System.out.println("from a\n"); - lib.print_data(a); - System.out.println("\bfrom b\n"); - lib.print_data(b); - - // Try to bind to custom address - APIExt ext = Libraries.bind(APIExt.class, (String name)->{ - Scope ss = s.fork(); - Pointer cname = ss.allocateCString(name); - Pointer p = lib.api_func(cname); - ss.close(); - - return new Library.Symbol() { - public String getName() { - return name; - } - public Pointer getAddress() { - System.out.printf(" %s -> %016x\n", name, p.addr()); - return p; - } - }; - }); - - System.out.println("invoke ext interface"); - ext.funca(42); - System.out.printf("funb -> %d\n", ext.funcb((short)56)); - System.out.printf("func -> %d\n", ext.funcc(56.7198273918723f)); - - ext.funcd(12); - } - } -} diff --git a/test-api/src/api/test/TestAPI.java b/test-api/src/api/test/TestAPI.java new file mode 100644 index 0000000..2d8be3b --- /dev/null +++ b/test-api/src/api/test/TestAPI.java @@ -0,0 +1,69 @@ + +package api.test; + +import jdk.incubator.foreign.*; + +import proto.api.*; +import static proto.api.APILib.*; +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(3); //a.setC((byte)3); + a.setD(4); //a.setD((byte)4); + + + b.setA(5); + b.setB(6); + b.setC((byte)255); + b.setD((byte)255); + + b.setTest_a(cb); + + System.out.println("from a"); + print_data(a); + System.out.println("from b"); + print_data(b); + + //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(Memory.ByteArray.create("funca", frame)), scope); + System.out.printf(" %s\n", funca.symbol()); + funca.function().call(12); + + api api = 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-vulkan/Makefile b/test-vulkan/Makefile new file mode 100644 index 0000000..2ecde13 --- /dev/null +++ b/test-vulkan/Makefile @@ -0,0 +1,41 @@ + +JAVA_HOME=/opt/jdk-foreign/jvm/openjdk-19-internal +JAVAC=$(JAVA_HOME)/bin/javac +JAVA=$(JAVA_HOME)/bin/java + +zvk_TEMPLATES := $(wildcard template/*.java) +zvk_SOURCES := $(wildcard src/zvk/*.java) +zvk_demo_SOURCES := $(wildcard src/zvk/test/*.java) + +all:: + mkdir -p bin + +all:: bin/demo.built bin/classes/zvk/test/mandelbrot.bin + +bin/api.built: bin/api.gen $(zvk_SOURCES) + $(JAVAC) --add-modules jdk.incubator.foreign -d bin/classes \ + $(shell find bin/gen -name '*.java') \ + $(zvk_SOURCES) + touch $@ + +bin/api.gen: /usr/share/vulkan/registry/vk.xml generate-vulkan $(zvk_TEMPLATES) + PERL_HASH_SEED=0 ./generate-vulkan -d bin/gen + touch $@ + +bin/demo.built: bin/api.built $(zvk_demo_SOURCES) + $(JAVAC) --add-modules jdk.incubator.foreign -d bin/classes -cp bin/classes \ + $(zvk_demo_SOURCES) + touch $@ + +bin/classes/zvk/test/mandelbrot.bin: mandelbrot.comp + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +demo: bin/demo.built bin/classes/zvk/test/mandelbrot.bin + $(JAVA) --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign \ + -cp bin/classes zvk.test.TestVulkan + +clean: + rm -rf api bin mandelbrot.pam + +.PHONY: demo clean all diff --git a/test-vulkan/generate-vulkan b/test-vulkan/generate-vulkan new file mode 100755 index 0000000..7409ca4 --- /dev/null +++ b/test-vulkan/generate-vulkan @@ -0,0 +1,2443 @@ +#!/usr/bin/perl + +# -*- Mode:perl; perl-indent-level:4;tab-width:4; -*- + +# TODO: upcalls are just done manually, see template/PFN* +# TODO: check the extension function pointer setup/splitting is correct +# TODO: things that take a Memory.*Array probably don't need to also take a count, if one is defined in the registry +# TODO: the api constants, where to? +# TODO: vkDestroyDevice and vkDestroyInstance should close the ResourceScope they have + +use Data::Dumper; +use File::Path qw(make_path); + +require XML::Parser; + +# these can't really be changed yet +$targetDirectory = "api"; +$targetPackage = "zvk"; + +while (@ARGV) { + my $cmd = shift(@ARGV); + + if ($cmd eq "-t") { + $targetPackage = shift(@ARGV); + } elsif ($cmd eq "-d") { + $targetDirectory = shift(@ARGV); + } elsif ($cmd eq "-v") { + $verbose++; + } +} + +$xml = XML::Parser->new(Style => 'Objects'); +$doc = $xml->parsefile('/usr/share/vulkan/registry/vk.xml'); + +$registry = @{$doc}[0]; + +%toDrop = ( + VkBaseInStructure => 1, + VkBaseOutStructure => 1, + vkEnumeratePhysicalDeviceGroups => 1, + vkEnumeratePhysicalDevices => 1, + VkPhysicalDeviceGroupProperties => 1, + vkAllocateCommandBuffers => 1, + vkMapMemory => 1, + ); + +my %accessMode = ( + 'VkDebugUtilsMessengerCallbackDataEXT' => 3, # actually read-only? + 'VkClearColorValue' => 3, + 'VkCommandBufferAllocateInfo' => 3 + ); + + +# unused but this documents all INSTANCE creation functions +%dynamicInit = ( + vkCreateInstance => 1, + vkEnumeratePhysicalDevices => VkInstance, + vkCreateDevice => VkPhysicalDevice, + vkAllocateCommandBuffers => VkDevice, + vkGetDeviceQueue => VkDevice, + vkGetDeviceQueue2 => VkDevice, + + VkInstance => 'instance', + VkPhysicalDevice => 'instance,device',# dunno how that works + VkCommandBuffer => 'instance,device', # instance is only debugutils + VkDevice => 'instance,device', # instance is only debugutils + VkQueue => 'instance,device' # instance is only debugutils + + ); + +%typeToJavaPrimitive = ( + uint8_t => 'byte', char => 'byte', + uint16_t => 'short', + int32_t => 'int', uint32_t => 'int', int => 'int', + int64_t => 'long', uint64_t => 'long', size_t => 'long', + float => 'float', + double => 'double' + ); + +# must only include java primitives +%typeSizePrimitive = ( + "byte" => 8, + "short" => 16, + "int" => 32, + "long" => 64, + "float" => 32, + "double" => 64 + ); + +# pre-load types from external header files +%data = ( + 'VisualID' => { category => 'basetype', name => 'VisualID', type => 'uint64_t' }, + 'Window' => { category => 'basetype', name => 'Window', type => 'uint64_t' }, + 'xcb_visualid_t' => { category => 'basetype', name => 'xcb_visualid_t', type => 'uint32_t' }, + 'xcb_window_t' => { category => 'basetype', name => 'xcb_window_t', type => 'uint32_t' }, + 'HANDLE' => { category => 'basetype', name => 'HANDLE', type => 'VK_DEFINE_NON_DISPATCHABLE_HANDLE' }, + 'RROutput' => { category => 'basetype', name => 'RROutput', type => 'uint32_t' }, + 'zx_handle_t' => { category => 'basetype', name => 'zx_handle_t', type => 'uint64_t' }, + ); +# from API Constants section +%apiConstants = (); +%alias = (); +%commands = (); +@commandsList = (); +@features = (); +@extensions = (); +@bitmaskTypes = (); + +%commandsExported = (); + +# get something of the form +# ... xxx ... xxx +sub scanMember { + my $n = shift @_; + my $baseType = ""; + my $fullType = ""; + my $name = ""; + + # enum is for array sizes + foreach $p (@{$n->{Kids}}) { + if ($p->isa('Characters')) { + $fullType .= $p->{Text}; + } elsif ($p->isa('type')) { + $baseType = @{$p->{Kids}}[0]->{Text}; + $fullType .= $baseType; + } elsif ($p->isa('name')) { + $name = @{$p->{Kids}}[0]->{Text}; + } elsif ($p->isa('enum')) { + $fullType .= @{$p->{Kids}}[0]->{Text}; + } + } + + $fullType =~ s/^\s+|\s+$//g; + + my %member = ( + name => $name, + baseType => $baseType, + fullType => $fullType + ); + + $member{len} = $n->{len} if (defined $n->{len}); + $member{altlen} = $n->{altlen} if (defined $n->{altlen}); + $member{optional} = $n->{optional} if (defined $n->{optional}); + $member{values} = $n->{values} if (defined $n->{values}); + + return \%member; +} + +sub addRequire { + my $list = shift @_; + my $z = shift @_; + my $sourceName = shift @_; + my $source = shift @_; + my %req = ( + name => $z->{name}, + $sourceName => $source + ); + + $req{comment} = $z->{comment} if (defined($z->{comment})); + + if ($z->isa('type')) { + $req{category} = 'type'; + push @$list, \%req; + } elsif ($z->isa('command')) { + $req{category} = 'command'; + push @$list, \%req; + } elsif ($z->isa('enum')) { + $req{category} = "enum"; + $req{alias} = $z->{alias} if (defined($z->{alias})); + $req{bitpos} = $z->{bitpos} if (defined($z->{bitpos})); + $req{dir} = $z->{dir} if (defined($z->{dir})); + $req{extends} = $z->{extends} if (defined($z->{extends})); + $req{extnumber} = $z->{extnumber} if (defined($z->{extnumber})); + $req{offset} = $z->{offset} if (defined($z->{offset})); + $req{value} = $z->{value} if (defined($z->{value})); + push @$list, \%req; + } +} + +foreach $x (grep { $_->isa('types') } @{$registry->{Kids}}) { + foreach $t (grep { $_->isa('type') } @{$x->{Kids}}) { + if (!defined($t->{alias})) { + my $category = $t->{category}; + + if ($category eq 'struct' || $category eq 'union') { + my @members = (); + + foreach $m (grep { $_->isa('member') } @{$t->{Kids}}) { + push @members,scanMember($m); + } + + my %struct = ( + category => $category, + name => $t->{name}, + members => \@members, + bitAlignment => 64 + ); + + $data{$struct{name}} = \%struct; + } elsif ($category eq "handle") { + my $info = scanMember($t); + my %struct = ( + category => $category, + name => $info->{name}, + parent => $t->{parent}, + objtypeenum => $t->{objtypeenum}, + type => $info->{baseType}, + + bitSize => 64, + bitAlignment => 64 + ); + + $data{$struct{name}} = \%struct; + } elsif ($category eq "bitmask") { + # these map enums to the basic types but we can't use it yet, save for later + my %struct = (); + my $info = scanMember($t); + + $struct{category} = "enum:bitmask"; + $struct{name} = $info->{name}; + $struct{type} = $info->{baseType}; + # fuck knows what the difference is + $struct{requires} = $t->{requires} if (defined $t->{requires}); + $struct{bitvalues} = $t->{bitvalues} if (defined $t->{bitvalues}); + + push @bitmaskTypes, \%struct; + # added to %data later + } elsif ($category eq "basetype") { + my %struct = (); + my $info = scanMember($t); + + if ($info->{baseType}) { + $struct{category} = $category; + $struct{name} = $info->{name}; + $struct{type} = $info->{baseType}; + + # set holders here? + # TODO: fuck this off i think, it's not useful enough? + if ($info->{baseType} eq "uint32_t") { + $struct{bitSize} = 32; + $struct{bitAlignment} = 32; + } elsif ($info->{baseType} eq "uint64_t") { + $struct{bitSize} = 64; + $struct{bitAlignment} = 64; + } elsif ($info->{fullType} eq 'typedef void* ;') { + $struct{bitSize} = 64; + $struct{bitAlignment} = 64; + } else { + print "$info->{name} '$info->{baseType}' '$info->{fullType}'\n"; + die(); + } + + $data{$struct{name}} = \%struct; + } + } elsif ($category eq 'funcpointer') { + # typedef void (VKAPI_PTR *PFN_vkInternalAllocationNotification)( + # void* pUserData, + # size_t size, + # VkInternalAllocationType allocationType, + # VkSystemAllocationScope allocationScope); + my %struct = (); + my $fullType = ""; + my @oaramTypes = (); + + foreach $p (@{$t->{Kids}}) { + if ($p->isa('Characters')) { + $fullType .= $p->{Text}; + } elsif ($p->isa('type')) { + push @paramTypes, @{$p->{Kids}}[0]->{Text}; + $fullType .= @{$p->{Kids}}[0]->{Text}; + } elsif ($p->isa('name')) { + $struct{name} = @{$p->{Kids}}[0]->{Text}; + $fullType .= @{$p->{Kids}}[0]->{Text}; + } + } + $fullType =~ s/^\s+|\s+$//g; + $fullType =~ s/\n|VKAPI_PTR//g; + $fullType =~ s/ +/ /g; + $fullType =~ s/\( +/\(/g; + + $struct{prototype} = $fullType; + $data{$struct{name}} = \%struct; + + %struct = (); + $struct{prototype} = $fullType; + $fullType =~ m/typedef (.*) \(\*(.*)\)\((.*)\)/; + $struct{result} = $1; + $struct{name} = $2; + $struct{args} = $3; + foreach $arg (split /,/,$struct{args}) { + $arg =~ m/^([^\*]+)(\*)? (.+)$/; + push @{$struct{params}}, { name => $3, fullType => $1.$2, baseType=>$1 }; + } + #print Dumper(\%struct); + } + } else { + $alias{$t->{name}} = $t->{alias}; + } + } +} + +foreach $x (grep { $_->isa('enums') } @{$registry->{Kids}}) { + if ($x->{type} eq "enum") { + my @members = (); + foreach $t (grep { $_->isa('enum') } @{$x->{Kids}}) { + my %info = ( + name => $t->{name} + ); + $info{value} = $t->{value} if (defined($t->{value})); + $info{comment} = $t->{comment} if (defined($t->{comment})); + $info{alias} = $t->{alias} if (defined($t->{alias})); + push @members, \%info; + } + my %enum = ( + category => 'enum', + name => $x->{name}, + members => \@members + ); + $data{$enum{name}} = \%enum; + } elsif ($x->{type} eq 'bitmask') { + my @members = (); + foreach $t (grep { $_->isa('enum') } @{$x->{Kids}}) { + my %info = ( name => $t->{name} ); + + # FIXME: handle alias + $info{alias} = $t->{alias} if (defined($t->{alias})); + $info{comment} = $t->{comment} if (defined($t->{comment})); + $info{value} = $t->{value} if (defined($t->{value})); + $info{value} = "".(1<<$t->{bitpos}) if (defined($t->{bitpos})); + + push @members, \%info; + } + my %enum = ( + category => "enum:bitmask", + name => $x->{name}, + members => \@members + ); + $data{$enum{name}} = \%enum; + } else { + #defines here + foreach $t (grep { $_->isa('enum') } @{$x->{Kids}}) { + if (!defined($t->{alias})) { + my %enum = ( + category => 'enum:define', + name => $t->{name}, + value => $t->{value}, + type => $t->{type} + ); + $enum{comment} = $t->{comment} if (defined($t->{comment})); + $data{$enum{name}} = \%enum; + $apiConstants{$enum{name}} = \%enum; + } else { + $alias{$t->{name}} = $t->{alias}; + } + } + } +} + +foreach $x (grep { $_->isa('commands') } @{$registry->{Kids}}) { + foreach $y (grep { $_->isa('command') } @{$x->{Kids}}) { + if (!defined($y->{alias})) { + my %cmd = (); + my @params = (); + + $cmd{successcodes} = $y->{successcodes} if (defined $y->{successcodes}); + $cmd{errorcodes} = $y->{errorcodes} if (defined $y->{errorcodes}); + + foreach $z (@{$y->{Kids}}) { + if ($z->isa('proto')) { + $cmd{proto} = scanMember($z); + } elsif ($z->isa('param')) { + push @params, scanMember($z); + } + } + + $cmd{params} = \@params; + + my $name = $cmd{proto}->{name}; + + if ($cmd{proto}->{fullType} eq "") { + print Dumper($y); + die(); + } + $cmd{name} = $name; + $commands{$cmd{name}} = \%cmd; + push @commandsList, \%cmd; + + } else { + # want forward ref or not? + $alias{$y->{name}} = $y->{alias}; + } + } +} + +foreach $x (grep { $_->isa('feature') } @{$registry->{Kids}}) { + my %feature = ( + api => $x->{api}, + name => $x->{name}, + number => $x->{number}, + comment => $x->{comment} + ); + my @requires = (); + foreach $y (grep { $_->isa('require') } @{$x->{Kids}}) { + foreach $z (@{$y->{Kids}}) { + addRequire(\@requires, $z, 'feature', \%feature); + } + } + $feature{requires} = \@requires; + + push @features, \%feature; +} + +foreach $x (grep { $_->isa('extensions') } @{$registry->{Kids}}) { + foreach $y (grep { $_->isa('extension') } @{$x->{Kids}}) { + my %extension = ( + name => $y->{name}, + number => $y->{number}, + supported => $y->{supported}, + type => $y->{type} + ); + my @requires = (); + + $extension{requiresExtension} = $y->{requires} if (defined($y->{requires})); + $extension{platform} = $y->{platform} if (defined($y->{platform})); + + foreach $z (grep { $_->isa('require') } @{$y->{Kids}}) { + foreach $a (@{$z->{Kids}}) { + addRequire(\@requires, $a, 'extension', \%extension); + } + } + + $extension{requires} = \@requires; + + push @extensions,\%extension; + } +} + +my %dump = (); + +sub patchRequires { + my $x = shift @_; + my $r = shift @_; + + if ($r->{category} eq "type") { + $dump{$r->{name}} = $r; + } elsif ($r->{category} eq "command") { + $dump{$r->{name}} = $r; + } elsif ($r->{category} eq "enum") { + if ($r->{extends}) { + my $type = $data{$r->{extends}}; + my %info = ( name => $r->{name} ); + + $info{comment} = $r->{comment} if (defined($r->{comment})); + if (defined($r->{value})) { + $info{value} = $r->{value}; + } elsif (defined($r->{bitpos})) { + $info{value} = "".(1<<$r->{bitpos}) if (defined($r->{bitpos})); + } elsif (defined($r->{extnumber})) { + $info{value} = "".(1000000000 + 1000 * ($r->{extnumber} - 1) + $r->{offset}); + } elsif (defined($r->{offset})) { + $info{value} = $r->{dir}."".(1000000000 + 1000 * ($x->{number} - 1) + $r->{offset}); + } elsif (defined($r->{alias})) { + #print "ignoring enum alias: '$r->{alias}' -> '$r->{name}'\n"; + $info{alias} = $r->{alias}; + } else { + print Dumper($r); + die(); + } + + push @{$type->{members}}, \%info; + $dump{$r->{extends}} = { category => 'enum', name => $r->{extends} }; + } else { + my %enum = ( name => $r->{name} ); + + # this is only needed for two values, one int, the other a string + $enum{value} = $r->{value}; + $enum{category} = 'enum:define'; + $enum{type} = 'const char *' if ($r->{value} =~ m/^".*"$/); + $enum{type} = 'uint32_t' if ($r->{value} =~ m/^[0-9]*$/); + $enum{extension} = $x->{name}; + + die() if (!defined($enum{type})); + + # extension? do i care? + $dump{$enum{name}} = { category => 'enum:define', name => $enum{name} }; + $data{$enum{name}} = \%enum; + + } + } else { + die(); + } +} + +# collate types in each interface +# TODO: versions separated? or define one version? +foreach $x (@features) { + my @requires = @{$x->{requires}}; + + foreach $r (@requires) { + patchRequires($x, $r); + } +} + +# patch extensions in and collate all types to include in output +# TODO: there's various versioning crap here too +foreach $x (grep { $_->{supported} eq 'vulkan' + && (!defined($_->{platform}) || $_->{platform} =~ m/xlib|wayland|xcb/) } @extensions) { + my @requires = @{$x->{requires}}; + + foreach $r (@requires) { + patchRequires($x, $r); + } +} + +# fix up bitmask type bases +foreach $x (@bitmaskTypes) { + if (defined $x->{requires}) { + if (defined $data{$x->{requires}}) { + my $struct = $data{$x->{requires}}; + $struct->{type} = $x->{type}; + } else { + print "unknown bitmask enum requires $x->{requires}\n"; + } + # somehow redirect flags to requires? + $alias{$x->{name}} = $x->{requires}; + } elsif (defined $x->{bitvalues}) { + if (defined $data{$x->{bitvalues}}) { + my $struct = $data{$x->{bitvalues}}; + $struct->{type} = $x->{type}; + } else { + print "unknown bitmask enum bitvalues $x->{bitvalues}\n"; + } + # somehow redirect flags to requires? + $alias{$x->{name}} = $x->{bitvalues}; + } elsif (defined $data{$x->{name}}) { + my $struct = $data{$x->{name}}; + $struct->{type} = $x->{type}; + } else { + # these are referenced but don't have any definitions + my %enum = ( + category => $x->{category}, + name => $x->{name}, + members => \@members, + type => $x->{type} + ); + $data{$enum{name}} = \%enum; + } +} + +# Analyse length optioons +foreach $x (sort keys %dump) { + my $d = $dump{$x}; + my $name = $d->{name}; + + if ($d->{category} eq "command") { + analyseLengthParameters($commands{$name}); + } elsif ($d->{category} eq "struct") { + analyseLengthMembers($data{$name}); + } +} + +# This splits functions into class-based groups +# i.e. those with a handle as first argument +# the remaining ones are all VkInstance related static methods. +%functionSets = ( static => [] ); +foreach $x (sort keys %dump) { + my $d = $dump{$x}; + + if ($d->{category} eq "command") { + my $name = $d->{name}; + + $name = $alias{$name} if (defined $alias{$name}); + + my $cmd = $commands{$name}; + my @params = @{$cmd->{params}}; + + if ($#params >= 0 && defined($data{$params[0]->{fullType}}->{type})) { + my $t = $params[0]->{fullType}; + + if (defined($functionSets{$t})) { + push @{$functionSets{$t}}, $d->{name}; + } else { + my @list = ( $d->{name} ); + $functionSets{$t} = \@list; + } + #$set{$params[0]->{fullType}} = 1; + #push @{$set{$params[0]->{fullType}}}, $d->{name}; + } else { + #print Dumper($d); + push @{$functionSets{static}}, $d->{name}; + } + } +} + +# create list of extensions by type - instance or device - and set extensionType on each function +%functionByExtensionType = (); +foreach $y (keys %functionSets) { + my $x = $functionSets{$y}; + foreach $c (@{$x}) { + next if ($alias{$c}); + my $cmd = $commands{$c}; + + my $d = $dump{$cmd->{name}}; + my $ext = $d->{extension}; + if (defined $ext) { + my $type = $ext->{type}; + + # sigh, some physicaldevice or instance calls come from device extensions... but it's parent is only the instance + # so make sure they're actually in that type + + my @params = @{$cmd->{params}}; + if ($params[0]->{baseType} =~ m/^VkInstance|VkPhysicalDevice$/) { + $type = "instance"; + } + + push @{$functionByExtensionType{$type}}, $c; + $cmd->{extensionType} = $type; + $cmd->{extensionSource} = $ext->{type}; + $cmd->{extensionName} = $ext->{name}; + } + } +} + + +# TODO: unused +sub findType { + my $next = shift @_; + my $type; + + # basetypes? + + print"search $next\n"; + do { + $type = $next; + if (defined($alias{$type})) { + $next = $alias{$type}; + print " goto alias\n"; + } elsif (defined($data{$type})) { + my $d = $data{$type}; + + if ($d->{category} eq "struct") { + return $d; + } elsif ($d->{category} eq "enum") { + return $d; + } elsif ($d->{category} eq "basetype") { + return $d; + } else { + print "category: $d->{category}\n"; + die("fuckoff"); + } + } else { + return $type; + } + print" $next\n"; + } while (defined $next); + + return undefined; +} + +sub functionDescriptorType { + my $p = shift @_; + my $baseType = $p->{baseType}; + my $fullType = $p->{fullType}; + + if ($fullType =~ m/\*/) { + return "Memory.POINTER"; + } else { + my $type; + + # FIXME: This loops to handle a double-alias for some flag types + # FIXME: FlagsNV -> FlagsKHR -> FlagBitsKHR + # FIXME: maybe fix it elsewhere + do { + $baseType = $alias{$baseType} if (defined($alias{$baseType})); + $type = $data{$baseType}; + + #print "\n\n$baseType = ".Dumper($type)."\n\n"; + + if (defined $type->{type}) { + $baseType = $type->{type}; + #print " has subtype -> $baseType\n"; + } elsif ($type->{category} eq "enum" || $type->{category} eq "enum:bitmask") { + $baseType = "int32_t"; + #print " is normal enm -> $baseType\n"; + } + } while ($baseType =~ m/Flags/ && defined($alias{$baseType})); + + return 'Memory.'.uc($typeToJavaPrimitive{$baseType}) if ($typeToJavaPrimitive{$baseType}); + + # handles and some hacks + if ($baseType =~ m/^PFN_/) { + return "Memory.POINTER"; + } elsif ($baseType eq "VK_DEFINE_NON_DISPATCHABLE_HANDLE") { + return "Memory.POINTER"; + } elsif ($baseType eq "VK_DEFINE_HANDLE") { + return "Memory.POINTER"; + } elsif ($baseType eq "VkFlags") { + return 'Memory.INT'; + } elsif ($baseType eq "VkFlags64") { + return 'Memory.LONG'; + } + + $type = $data{$baseType}; + + #print " ** Unhandled base type $baseType full=$fullType nextalias=$alias{$baseType} orig=$p->{baseType}\n"; + #print Dumper($type); + #die(); + + return "$type->{name}.LAYOUT"; + #die(); + } +} + +# yuck +sub functionJavaType { + my $p = shift @_; + my $baseType = $p->{baseType}; + my $fullType = $p->{fullType}; + my $type; + my $deref = $fullType =~ tr/*/*/; + + if ($deref > 0 || $fullType =~ m/\[.*\]/) { + + if ($p->{len} == 'null-terminated' && $fullType eq 'const char*') { + return 'String'; + } elsif ($fullType eq "void*") { + return "MemoryAddress"; + } elsif ($p->{len} && $fullType eq 'const char* const*') { + return 'String[]'; + } + + $baseType = $alias{$baseType} if (defined($alias{$baseType})); + $type = $data{$baseType}; + + # TODO: if len or altlen set, then it's probably an array otherwise it's a holder/pointer + + if (defined $type->{type}) { + $baseType = $type->{type}; + #print " has subtype -> $baseType\n"; + } elsif ($type->{category} eq "enum" || $type->{category} eq "enum:bitmask") { + $baseType = "int32_t"; + #print " is normal enm -> $baseType\n"; + } + + # alias? + return 'Memory.'.ucfirst($typeToJavaPrimitive{$baseType}).'Array' if (defined $typeToJavaPrimitive{$baseType}); + + # handles and some hacks + if ($baseType eq "void") { + return "Memory.PointerArray"; + #return "void"; + } elsif ($baseType =~ m/^PFN_/) { + return "MemoryAddress"; + #return $type->{name}; + } elsif ($baseType eq "VK_DEFINE_NON_DISPATCHABLE_HANDLE") { + # typed array? + # We actually just want the same type since it's also an array if required + return "Memory.HandleArray<$p->{baseType}>"; + #return $type->{name}; + } elsif ($baseType eq "VK_DEFINE_HANDLE") { + # could be more typed + return "Memory.HandleArray<$p->{baseType}>"; + #return "Memory.PointerArray"; + #return $type->{name}; + } elsif ($baseType eq "VkFlags") { + return 'Memory.IntArray'; + } elsif ($baseType eq "VkFlags64") { + return 'Memory.LongArray'; + } + + if (defined($alias{$baseType})) { + $baseType = $alias{$baseType}; + } + $type = $data{$baseType}; + + # TODO: function poitners? + #print "lookup $fullType -> unhandled base type $baseType -> $type->{name}\n"; + + if (defined($type->{name})) { + return $type->{name}; + } else { + return "MemorySegment"; + } + } else { + # FIXME: This loops to handle a double-alias for some flag types + # FIXME: FlagsNV -> FlagsKHR -> FlagBitsKHR + # FIXME: maybe fix it elsewhere + do { + $baseType = $alias{$baseType} if (defined($alias{$baseType})); + $type = $data{$baseType}; + + #print "\n\n$baseType = ".Dumper($type)."\n\n"; + + if (defined $type->{type}) { + $baseType = $type->{type}; + #print " has subtype -> $baseType\n"; + } elsif ($type->{category} eq "enum" || $type->{category} eq "enum:bitmask") { + $baseType = "int32_t"; + #print " is normal enm -> $baseType\n"; + } + } while ($baseType =~ m/Flags/ && defined($alias{$baseType})); + + return $typeToJavaPrimitive{$baseType} if (defined $typeToJavaPrimitive{$baseType}); + + # handles and some hacks + if ($baseType eq "void") { + return "void"; + } elsif ($baseType =~ m/^PFN_/) { + return "MemoryAddress"; + #return $type->{name}; + } elsif ($baseType eq "VK_DEFINE_NON_DISPATCHABLE_HANDLE") { + return $type->{name}; + } elsif ($baseType eq "VK_DEFINE_HANDLE") { + return $type->{name}; + } elsif ($baseType eq "VkFlags") { + return 'int'; + } elsif ($baseType eq "VkFlags64") { + return 'long'; + } + + if (defined($alias{$baseType})) { + $baseType = $alias{$baseType}; + } + $type = $data{$baseType}; + + # TODO: function poitners? + #print "lookup $fullType -> unhandled base type $baseType -> $type->{name}\n"; + + if (defined($type->{name})) { + return $type->{name}; + } else { + return "Object"; + } + } +} + +sub formatFunctionDescriptor { + my $cmd = shift @_; + my $proto = $cmd->{proto}; + my @params = @{$cmd->{params}}; + + my $type = $data{$proto->{baseType}}; + my $nargs = 0; + my $desc; + + if ($proto->{fullType} eq "void") { + $desc = "FunctionDescriptor.ofVoid("; + } else { + $desc = "FunctionDescriptor.of("; + $desc .= functionDescriptorType($proto); + $nargs++; + } + + foreach $param (@params) { + $desc .= "," if ($nargs++ > 0); + $desc .= functionDescriptorType($param); + $desc .= ".withName(\"$param->{name}\")"; + } + $desc .= ")"; + return prettyFormat($desc); +} + +sub find { + my $n = shift @_; + my @list = @{shift @_}; + + foreach $p (@list) { + return $p if ($p->{name} eq $n); + } + return; +} + +# return the core root type, will be VK_DEFINE_HANDLE for handles, etc +sub getRootType { + my $baseType = shift @_; + my $type; + + $baseType = $alias{$baseType} if (defined($alias{$baseType})); + $type = $data{$baseType}; + + if (defined $type->{type}) { + $baseType = $type->{type}; + #print " has subtype -> $baseType\n"; + } elsif ($type->{category} eq "enum" || $type->{category} eq "enum:bitmask") { + $baseType = "int32_t"; + #print " is normal enm -> $baseType\n"; + } + + return $baseType; +} + +sub analyseLengthParameters { + my $cmd = shift @_; + my @params = @{$cmd->{params}}; + + foreach $param (@params) { + if ($param->{len} eq 'null-terminated') { + if ($param->{fullType} eq 'const char*') { + $param->{lenType} = 'string'; + } else { + die ("unsupported function length type $param->{fullType}"); + } + } elsif ($param->{len} =~ m/^(.+),?/) { + my $count = $1; + my $src = find($count, \@params); + + $src->{lenTarget} = $param->{name}; + + # if count is a pointer then it's for returns + # find out target type to determine structure of parameter + my $rootType = getRootType($param->{baseType}); + my $fullType = $param->{fullType}; + my $nderef = $fullType =~ tr/*/*/; + my $lenType = 'raw'; + + if ($nderef == 1 && defined $typeToJavaPrimitive{$rootType}) { + $lenType = 'primitive-array'; + } elsif ($rootType eq "VK_DEFINE_NON_DISPATCHABLE_HANDLE") { + $lenType = 'handle-array'; + } elsif ($rootType eq "VK_DEFINE_HANDLE") { + $lenType = 'handle-array'; + } elsif ($nderef == 2) { + $lenType = 'pointer-array'; + } else { + $lenType = 'struct-array'; + } + + $src->{lenType} = $lenType.'-count'; + $param->{lenType} = $lenType; + } + } +} + +# can merge with above? +# not used yet tho +sub analyseLengthMembers { + my $cmd = shift @_; + my @params = @{$cmd->{members}}; + + foreach $param (@params) { + if ($param->{len} eq 'null-terminated') { + if ($param->{fullType} eq 'const char*') { + $param->{lenType} = 'string'; + } else { + die ("unsupported function length type $param->{fullType}"); + } + } elsif ($param->{len} =~ m/^(.+),?/) { + my $count = $1; + my $src = find($count, \@params); + + $src->{lenTarget} = $param->{name}; + + # find out target type to determine structure of parameter + my $rootType = getRootType($param->{baseType}); + my $fullType = $param->{fullType}; + my $nderef = $fullType =~ tr/*/*/; + my $lenType = 'raw'; + + if ($nderef == 1 && defined $typeToJavaPrimitive{$rootType}) { + $lenType = 'primitive-array'; + } elsif ($rootType eq "VK_DEFINE_NON_DISPATCHABLE_HANDLE") { + $lenType = 'handle-array'; + } elsif ($rootType eq "VK_DEFINE_HANDLE") { + $lenType = 'handle-array'; + } elsif ($nderef == 2) { + $lenType = 'pointer-array'; + } else { + $lenType = 'struct-array'; + } + + $src->{lenType} = $lenType.'-count'; + $param->{lenType} = $lenType; + } + } +} + +# TBD +sub dumpFunctionPrototype { + my $cmd = shift @_; + my $proto = $cmd->{proto}; + my @params = @{$cmd->{params}}; + + my $type = $data{$proto->{baseType}}; + my $nargs = 0; + + if ($proto->{fullType} eq "void") { + print "void"; + } else { + print functionJavaType($proto); + } + print " $proto->{name}("; + + foreach $param (@params) { + my $lenType = $param->{lenType}; + my $fullType = $param->{fullType}; + my $cderef = ($fullType =~ tr/*/*/); + + #print "\n[ $param->{name} lentype=$lenType ]\n"; + + next if ($cderef == 0 && $lenType eq 'primitive-array-count'); + next if ($cderef == 0 && $lenType eq 'handle-array-count'); + # keep raw-count, struct-array-count and output counts + + print ', ' if ($nargs++ > 0); + + + if ($lenType =~ m/-count$/ && $cderef > 0) { + # count is an output + print $typeToJavaPrimitive{getRootType($param->{baseType})}.'[]'; + } elsif ($lenType eq 'string') { + print 'String'; + } elsif ($lenType eq 'handle-array') { + # alias? + print $param->{baseType}.'[]'; + } elsif ($lenType eq 'primitive-array') { + print $typeToJavaPrimitive{getRootType($param->{baseType})}.'[]'; + } elsif ($lenType eq 'pointer-array') { + # what do i want here, MemoryAddress[]? + # or just MemorySegment + print 'MemorySegment'; + } elsif ($lenType eq 'struct-array') { + print 'MemorySegment'; + } else { + print functionJavaType($param); + } + print ' '; + print $param->{name}; + } + print ')'; +} + +sub formatPrototype { + my $cmd = shift @_; + my $static = shift @_; + + my $proto = $cmd->{proto}; + my @params = @{$cmd->{params}}; + + my $nargs = 0; + my $jrtype = proto->{fullType} eq 'void' ? 'void' : functionJavaType($proto); + my $desc; + + # TODO: don't include return type if it's success + $desc = ($cmd->{successcodes} eq 'VK_SUCCESS') ? $desc = 'void' : $jrtype; + $desc .= " $proto->{name}("; + + # TODO: static or not + foreach $param (@params [($static ? 0 : 1) .. $#params]) { + my $fullType = $param->{fullType}; + my $deref = ($fullType =~ tr/*/*/); + + # implied counts + next if ($deref == 0 && $lenType eq 'primitive-array-count'); + next if ($deref == 0 && $lenType eq 'handle-array-count'); + + $desc .= ', ' if ($nargs++ > 0); + + my $tname = functionJavaType($param); + $tname = $alias{$tname} if (defined $alias{$tname}); + + $desc .= "$tname $param->{name}"; + } + $desc .= ')'; + + return ($jrtype, $desc); +} + +# TODO: merge with above, maybe pass in params list for different rnage +# TODO: also return type type +sub formatCreatePrototype { + my $cmd = shift @_; + my $static = shift @_; + + my $proto = $cmd->{proto}; + my @params = @{$cmd->{params}}; + + my $nargs = 0; + my $jrtype = proto->{fullType} eq 'void' ? 'void' : functionJavaType($proto); + my $desc; + + # TODO: don't include return type if it's success + #$desc = ($cmd->{successcodes} eq 'VK_SUCCESS') ? $desc = 'void' : $jrtype; + $desc = $params[$#params]->{baseType}; + $desc .= " $proto->{name}("; + + # TODO: static or not + foreach $param (@params [($static ? 0 : 1) ..$#params-1]) { + my $fullType = $param->{fullType}; + my $deref = ($fullType =~ tr/*/*/); + + # implied counts + next if ($deref == 0 && $lenType eq 'primitive-array-count'); + next if ($deref == 0 && $lenType eq 'handle-array-count'); + + $desc .= ', ' if ($nargs++ > 0); + + my $tname = functionJavaType($param); + $tname = $alias{$tname} if (defined $alias{$tname}); + + $desc .= "$tname $param->{name}"; + } + $desc .= ')'; + + return ($jrtype, $desc); +} + +# exports a non-static member function +# TODO: handle static too +sub exportFunction { + my $f = shift @_; + my $cmd = shift @_; + my $static = shift @_; + my ($jrtype, $prototype) = formatPrototype($cmd, $static); + my $index = 0; + my @params = @{$cmd->{params}}; + + $commandsExported{$cmd->{proto}->{name}} = 1; + #print "$jrtype\n"; + #print Dumper($cmd); + + die("cannot have static extensions") if ($static && $cmd->{extensionType}); + + print $f "\tpublic "; + print $f "static " if $static; + print $f $prototype; + print $f "throws Exception" if ($cmd->{successcodes}); + print $f " {\n"; + + # TODO: only create frame if required? + + print $f "\t\t$jrtype res\$code;\n" if ($jrtype ne 'void'); + print $f "\t\ttry (Frame frame = Memory.createFrame()) {\n"; + + # setup + foreach $param (@params[($static ? 0 : 1) .. $#params]) { + my $tname = functionJavaType($param); + $tname = $alias{$tname} if (defined $alias{$tname}); + + # TODO: if it's an output only then don't copy the count? + if ($tname eq 'String') { + print $f "\t\t\tMemorySegment $param->{name}\$h = frame.copy($param->{name});\n"; + } + } + # invoke + print $f "\t\t\t"; + print $f "res\$code = ($jrtype)" if ($jrtype ne 'void'); + print $f "$cmd->{extensionType}Dispatch." if ($cmd->{extensionType}); + print $f "$cmd->{name}\$FH.invokeExact(\n"; + + $index = 0; + if (!$static) { + print $f "\t\t\t\t(Addressable)address()"; + print $f ",\n" if ($index++ > 0); + } + + foreach $param (@params[($static ? 0 : 1) .. $#params]) { + my $tname = functionJavaType($param); + $tname = $alias{$tname} if (defined $alias{$tname}); + + print $f ",\n" if ($index++ > 0); + + if ($tname eq 'String') { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name}\$h)"; + } elsif ($tname =~ m/Memory\..*Array/) { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name})"; + } elsif ($data{$tname}) { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name})"; + } elsif ($tname eq 'MemorySegment' || $tname eq 'MemoryAddress') { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name})"; + } else { + print $f "\t\t\t\t($tname)$param->{name}"; + } + } + print $f ");\n"; + + # TODO: create functions how??? + + # copy any output data back + foreach $param (@params) { + my $tname = functionJavaType($param); + $tname = $alias{$tname} if (defined $alias{$tname}); + + next if ($param->{fullType} =~ m/const/); + + if ($tname =~ m/\[\]$/) { + print $f "\t\t\tMemorySegment.ofArray($param->{name}).copyFrom($param->{name}\$h);\n"; + } + } + + if ($cmd->{successcodes}) { + my $returnStatement = ($cmd->{successcodes} eq 'VK_SUCCESS') ? 'return' : 'return res$code'; + foreach $r (split /,/, $cmd->{successcodes}) { + print $f "\t\t\tif (res\$code == VkResult.$r) $returnStatement;\n"; + } + } elsif ($jrtype ne "void") { + print $f "\t\t\treturn res\$code;\n"; + } + + print $f "\t\t} catch (Throwable t) { throw new RuntimeException(t); }\n"; + if ($cmd->{successcodes}) { + # fixme, some exception type(s) + print $f "\t\tthrow new Exception(String.format(\"failcode %d\", res\$code));\n"; + } + + print $f "\t}\n"; +} + +# create functions return a handle in their last argument +sub exportCreateFunction { + my $f = shift @_; + my $cmd = shift @_; + my $static = shift @_; + my ($jrtype, $prototype) = formatCreatePrototype($cmd, $static); + my $index = 0; + my @params = @{$cmd->{params}}; + my $instanceType = $params[$#params]->{baseType}; + + $commandsExported{$cmd->{proto}->{name}} = 1; + + # CHANGE: static + print $f "\tpublic "; + print $f 'static ' if ($static); + print $f $prototype; + print $f "throws Exception" if ($cmd->{successcodes}); + print $f " {\n"; + + print $f "\t\t$jrtype res\$code;\n" if ($jrtype ne 'void'); + print $f "\t\ttry (Frame frame = Memory.createFrame()) {\n"; + + # setup + # CHANGE: return holder + print $f "\t\t\tMemorySegment instance\$h = frame.allocatePointer();\n"; + + # CHANGE: range + foreach $param (@params[($static ? 0 : 1) .. $#params-1]) { + my $tname = functionJavaType($param); + $tname = $alias{$tname} if (defined $alias{$tname}); + + # TODO: if it's an output only then don't copy the count? + if ($tname eq 'String') { + print $f "\t\t\tMemorySegment $param->{name}\$h = frame.copy($param->{name});\n"; + } + } + # invoke + print $f "\t\t\t"; + print $f "res\$code = ($jrtype)" if ($jrtype ne 'void'); + print $f "$cmd->{extensionType}Dispatch." if ($cmd->{extensionType}); + print $f "$cmd->{name}\$FH.invokeExact(\n"; + + $index = 0; + # CHANGE: no 'this' + if (!$static) { + print $f "\t\t\t\t(Addressable)address()"; + print $f ",\n" if ($index++ > 0); + } + + # CHANGE: range + foreach $param (@params[($static ? 0 : 1) .. $#params-1]) { + my $tname = functionJavaType($param); + $tname = $alias{$tname} if (defined $alias{$tname}); + + print $f ",\n" if ($index++ > 0); + + if ($tname eq 'String') { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name}\$h)"; + } elsif ($tname =~ m/Memory\..*Array/) { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name})"; + } elsif ($data{$tname}) { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name})"; + } elsif ($tname eq 'MemorySegment' || $tname eq 'MemoryAddress') { + print $f "\t\t\t\t(Addressable)Memory.address($param->{name})"; + } else { + print $f "\t\t\t\t($tname)$param->{name}"; + } + } + # CHANGE: return pointer + print $f ",\n" if ($index++ > 0); + print $f "\t\t\t\t(Addressable)instance\$h.address()"; + + print $f ");\n"; + + # CHANGE: no output data + # # copy any output data back + # foreach $param (@params) { + # my $tname = functionJavaType($param); + # $tname = $alias{$tname} if (defined $alias{$tname}); + + # next if ($param->{fullType} =~ m/const/); + + # if ($tname =~ m/\[\]$/) { + # print $f "\t\t\tMemorySegment.ofArray($param->{name}).copyFrom($param->{name}\$h);\n"; + # } + # } + + # CHANGE: different return code processing + # if ($cmd->{successcodes}) { + # my $returnStatement = ($cmd->{successcodes} eq 'VK_SUCCESS') ? 'return' : 'return res$code'; + # foreach $r (split /,/, $cmd->{successcodes}) { + # print $f "\t\tif (res\$code == VkResult.$r) $returnStatement;\n"; + # } + # } else { + # print $f "\t\treturn $res\$code;\n"; + # } + + # FIXME: see vkCreateGraphicsPipelines multiple 'success' types + # This might cause leaks? + print $f "\t\t\tif (res\$code == VkResult.VK_SUCCESS)\n\t" if ($jrtype ne 'void'); + + # initialise extension function tables if required + if ($data{$instanceType}->{type} eq "VK_DEFINE_HANDLE") { + if ($instanceType eq "VkInstance") { + print $f "\t\t\treturn $instanceType.create(instance\$h.get(Memory.POINTER, 0));\n"; + } elsif ($instanceType =~ m/^VkDevice|VkPhysicalDevice$/) { + print $f "\t\t\treturn $instanceType.create(instance\$h.get(Memory.POINTER, 0), instanceDispatch);\n"; + } else { + print $f "\t\t\treturn $instanceType.create(instance\$h.get(Memory.POINTER, 0), instanceDispatch, deviceDispatch);\n"; + } + } else { + print $f "\t\t\treturn $instanceType.create(instance\$h.get(Memory.POINTER, 0));\n"; + } + + print $f "\t\t} catch (Throwable t) { throw new RuntimeException(t); }\n"; + + # CHANGE: different return code processing + # if ($cmd->{successcodes}) { + # # fixme, some exception type(s) + # print $f "\t\tthrow new Exception(String.format(\"failcode %d\", res\$code));\n"; + # } + + print $f "\t\tthrow new Exception(String.format(\"failcode %d\", res\$code));\n" if ($jrtype ne 'void'); + + print $f "\t}\n"; +} + +%descToSize = ( + 'Memory.BYTE' => 8, + 'Memory.SHORT' => 16, + 'Memory.INT' => 32, + 'Memory.LONG' => 64, + 'Memory.FLOAT' => 32, + 'Memory.DOUBLE' => 64, + 'Memory.POINTER' => 64 + ); + +sub calcMemberSize { + my $m = shift @_; + my $desc = functionDescriptorType($m); + my $size; + my $align = 1; + + if (defined $descToSize{$desc}) { + $size = $descToSize{$desc}; + $align = $size; + } elsif ($desc =~ m/^(.+)\.LAYOUT/) { + #print "\n$desc\n"; + ($size, $align) = calcSize($data{$1}); + #print "\n"; + } else { + print Dumper($m); + print "description='$desc'\n"; + die("unknown size"); + } + + # handle array types + if ($m->{fullType} =~ m/\[(\d+)\]/) { + #print "array size $m->{fullType} = $1\n"; + $size *= $1; + } elsif ($m->{fullType} =~ m/\[(.+)\]/) { + my $value = $apiConstants{$1}->{value}; + #print "array size $m->{fullType} = $1 = $value\n"; + $size *= $value; + } + + #print "member $m->{name} size=$size align=$align\n"; + #print Dumper($m); + #my $sizeb = $size / 8; + #my $alignb = $align / 8; + #print "size=$size ($sizeb) align=$align ($alignb) desc=$desc\n\n"; + + return ($size, $align); +} + +sub calcSize { + my $s = shift @_; + my $sizeof = 0; + + return ($s->{bitSize}, $s->{bitAlign}) if (defined $s->{bitSize}); + + if ($s->{category} eq "struct") { + my $maxAlign = 1; + + for $m (@{$s->{members}}) { + my ($size, $align) = calcMemberSize($m); + + # my $desc = functionDescriptorType($m); + # my $size; + # my $align = 1; + + # if (defined $descToSize{$desc}) { + # $size = $descToSize{$desc}; + # } elsif ($desc =~ m/^(.+)\.LAYOUT/) { + # $size = calcSize($data{$1}); + # } + + $maxAlign = $align if $align > $maxAlign; + + # if ($size >=8 && $size <= 64) { + # $align = $size; + # } elsif ($size > 64) { + # $align = 64; + # } else { + # $align = 1; + # } + + $sizeof = ($sizeof + $align - 1) & ~($align - 1); + $sizeof += $size; + } + + $sizeof = ($sizeof + $maxAlign - 1) & ~($maxAlign-1); + $s->{bitAlign} = $maxAlign; + $s->{bitSize} = $sizeof; + } elsif ($s->{category} eq "union") { + my $maxAlign = 1; + + for $m (@{$s->{members}}) { + my $desc = functionDescriptorType($m); + my $size; + my $align = 1; + + if (defined $descToSize{$desc}) { + $size = $descToSize{$desc}; + } elsif ($desc =~ m/^(.+)\.LAYOUT/) { + $size = calcSize($data{$1}); + } + + $sizeof = $size if ($size > $sizeof); + } + + # FIXME: probably wrong + $sizeof = ($sizeof + 63) & ~(63); + $s->{bitAlign} = 64; + $s->{bitSize} = $sizeof; + } elsif ($s->{category} eq "enum:bitmask" || $s->{category} eq "enum") { + # hmm,? + if ($s->{type} eq "VkFlags64") { + $s->{bitSize} = 64; + $s->{bitAlign} = 64; + } else { + $s->{bitSize} = 32; + $s->{bitAlign} = 32; + } + } else { + print Dumper($s); + die(); + } + + return ($s->{bitSize}, $s->{bitAlign}); +} + +sub formatLayout { + my $s = shift @_; + my $layout; + + if ($s->{category} eq 'struct') { + my $sizeof = 0; + my $index = 0; + + $layout = 'MemoryLayout.structLayout('; + for $m (@{$s->{members}}) { + my $type = functionDescriptorType($m); + my ($size, $align) = calcMemberSize($m); + my $pad = $sizeof & ($align - 1); + + if ($pad) { + $layout .= ',' if ($index++ > 0); + $layout .= "MemoryLayout.paddingLayout($pad)"; + $sizeof += $pad; + } + + #if ($type =~ m/\.LAYOUT$/) { + { + if ($m->{fullType} =~ m/\[(\d+)\]/) { + my $value = $1; + #print "s array size $s->{name}.$m->{name} $m->{fullType} = $1\n"; + $type = "MemoryLayout.sequenceLayout($value,\t$type)"; + } elsif ($m->{fullType} =~ m/\[(.+)\]/) { + my $value = $apiConstants{$1}->{value}; + #print "s array size $s->{name}.$m->{name} $m->{fullType} = $1 = $value\n"; + $type = "MemoryLayout.sequenceLayout($value,\t$type)"; + } + } + + $layout .= ',' if ($index++ > 0); + $layout .= $type.".withName(\"$m->{name}\")"; + $sizeof += $size; + } + my $pad = $sizeof & 63; + $layout .= ',' if ($pad); + $layout .= "MemoryLayout.paddingLayout($pad)" if ($pad); + $layout .= ')'; + } elsif ($s->{category} eq "union") { + my $index = 0; + + $layout = 'MemoryLayout.unionLayout('; + for $m (@{$s->{members}}) { + my $type = functionDescriptorType($m); + my ($size, $align) = calcMemberSize($m); + + # not used + #if ($type =~ m/\.LAYOUT$/) { + { + if ($m->{fullType} =~ m/\[(\d+)\]/) { + my $value = $1; + #print "u array size $s->{name}.$m->{name} $m->{fullType} = $1\n"; + $type = "MemoryLayout.sequenceLayout($value, $type)"; + } elsif ($m->{fullType} =~ m/\[(.+)\]/) { + my $value = $apiConstants{$1}->{value}; + #print "u array size $s->{name}.$m->{name} $m->{fullType} = $1 = $value\n"; + $type = "MemoryLayout.sequenceLayout($value, $type)"; + } + } + $layout .= ',' if ($index++ > 0); + $layout .= $type.".withName(\"$m->{name}\")"; + $sizeof = $size if ($size > $sizeof); + } + my $pad = $sizeof & 63; + $layout .= ',' if ($pad); + $layout .= "MemoryLayout.paddingLayout($pad)" if ($pad); + $layout .= ')'; + } elsif ($s->{category} eq "enum") { + } elsif ($s->{category} eq "enum:bitmask") { + } else { + #print "X $s->{category}\n"; + } + + return prettyFormat($layout); +} + +sub prettyFormat { + my $java = shift @_; + + $java =~ s/\(/\(\n\t/; + $java =~ s/,/,\n\t/g; + + return $java; +} + +# + +#print 'VkAccelerationStructureGeometryTrianglesDataKHR'."\n"; + +# print formatLayout($data{'VkDeviceOrHostAddressConstKHR'}); + +# print formatLayout($data{'VkAccelerationStructureGeometryTrianglesDataKHR'}); +# print formatLayout($data{'VkApplicationInfo'}); +# print "\n"; + +# ($size,$align) = calcSize($data{'VkAccelerationStructureGeometryTrianglesDataKHR'}); +# $bytes = $size / 8; +# print "bytes=$bytes bits=$size align=$align\n"; +# exit 0; + +if (0) { + foreach $k (sort keys %functionSets) { + my $list = $functionSets{$k}; + + print "first: $k\n"; + + foreach $c (@$list) { + print " $c\n"; + } + } +} + + +sub updateAccess { + my $name = shift @_; + my $callType = shift @_; + + $name = $alias{$name} if (defined $alias{$name}); + + my $type = $data{$name}; + if ($type->{category} eq 'struct' || $type->{category} eq 'union') { + if ($callType =~ m/const/) { + $accessMode{$name} |= 2; + } else { + $accessMode{$name} |= 1; + } + + # handle any nested types too for this parameter + foreach $p (@{$type->{members}}) { + updateAccess($p->{baseType}, $callType); + } + } +} + +# determine which structures are input only or output only +# i.e. const or not-const +foreach $x (values %functionSets) { + foreach $y (@{$x}) { + my $cmd = $commands{$y}; + + foreach $p (@{$cmd->{params}}) { + updateAccess($p->{baseType}, $p->{fullType}); + } + } +} + +# look for all 'create' functions - last parameter is a writeable handle* +# also work out which ones are static or not - first parameter is (dispatchable?) handle +%functionCreate = (); +foreach $x (values %functionSets) { + foreach $y (@{$x}) { + my $cmd = $commands{$y}; + my @params = @{$cmd->{params}}; + + my $first = $params[0]; + my $last = $params[$#params]; + + # handle parent also? + my $firstFullType = $first->{fullType}; + my $lastFullType = $last->{fullType}; + + next if !defined $data{$first->{baseType}}; + next if !defined $data{$last->{baseType}}; + + my $static = !($data{$first->{baseType}}->{type} eq 'VK_DEFINE_HANDLE' + && ($firstFullType =~ tr/*/*/ == 0)); + my $create = (!($lastFullType =~ m/const/) + && ($lastFullType =~ tr/*/*/ == 1) + && !$last->{len} + && ($data{$last->{baseType}}->{type} eq 'VK_DEFINE_HANDLE' + || $data{$last->{baseType}}->{type} eq 'VK_DEFINE_NON_DISPATCHABLE_HANDLE')); + + if ($create) { + # print "Create function $y\n"; + # print " static\n" if $static; + # print '$data{$last->{baseType}}->{type} = '; + # print "$data{$last->{baseType}}->{type}\n"; + # print Dumper($cmd); + # print "first ".Dumper($first); + # print "last ".Dumper($last); + $functionCreate{$cmd->{proto}->{name}} = { + create => $create, + static => $static + }; + } + } +} + +# dump all to-dump types +if (0) { + foreach $x (sort keys %dump) { + my $d = $dump{$x}; + my $name = $d->{name}; + + $name = $alias{$name} if (defined $alias{$name}); + + print "----\n"; + print Dumper($d); + + if ($d->{category} eq "command") { + print Dumper($commands{$name}); + } else { + print Dumper($data{$name}); + } + print "\n"; + } +} + + + +# debug shit +#exportFunction(STDOUT, $commands{'vkGetDeviceProcAddr'}); + +if (0) { + print "layout\n"; + print formatLayout($data{'VkAccelerationStructureInfoNV'}); + print "\n"; + $name = 'VkAccelerationStructureInfoNV'; + while (defined($name)) { + my $s = $data{$name}; + + print "$name:\n"; + print Dumper($s); + $name = $alias{$name}; + } + exit 0; +} + +# ###################################################################### +# The main output routine + +#my $cmd = $commands{'vkGetQueueCheckpointDataNV'}; +#print Dumper($cmd); +#dumpFunctionPrototype($cmd); +#exit 0; + +$baseDir = $targetPackage; +$baseDir =~ s@\.@.@g; +$baseDir = $targetDirectory.'/'.$baseDir; + +make_path($baseDir); + +# dump all structure (and enum?) types +foreach $x (sort keys %dump) { + my $d = $dump{$x}; + + next if $toDrop{$d->{name}}; + next if ($d->{category} eq 'command'); + + my $name = $d->{name}; + my $s = $data{$name}; + + if ($s->{category} eq "struct" || $s->{category} eq 'union') { + my @members = @{$s->{members}}; + my $isTyped = $#members >= 1 + && $members[0]->{name} eq "sType" + && $members[1]->{name} eq "pNext"; + + # TODO: handle 'len' and 'altlen' for accessor setup + # TODO: find out which are input and which are output values + # TODO: could use the function parameters + + open(my $f, ">", "$baseDir/$s->{name}.java") || die("unable to open $baseDir/$s->{name}.java"); + + # look for complete override + if (open(my $template, "<", "template/$s->{name}.java")) { + print $f "// << inserted from: 'template/$s->{name}.java'\n"; + while (<$template>) { + print $f $_; + } + close ($template); + next; + } + + print $f "package $targetPackage;\n"; + print $f "import jdk.incubator.foreign.*;\n"; + print $f "import java.lang.invoke.*;\n"; + + print $f "public final class $s->{name} implements Memory.Addressable {\n"; + + print $f "\tfinal MemorySegment segment;\n"; + + print $f "\t$s->{name}(MemorySegment segment) {\n"; + print $f "\t\tthis.segment = segment;\n"; + if ($isTyped && $members[0]->{values}) { + print $f "\t\tsegment.set(Memory.INT, 0, VkStructureType.$members[0]->{values});\n"; + } + print $f "\t}\n"; + + print $f "\tpublic final MemoryAddress address() { return segment.address(); }\n"; + + # basic factory methods + print $f "\tpublic static $s->{name} create(Frame frame) {\n"; + print $f "\t\treturn new $s->{name}(frame.allocate(LAYOUT));\n"; + print $f "\t}\n\n"; + + if (!$isTyped) { + print $f "\tpublic static $s->{name} createArray(Frame frame, long count) {\n"; + print $f "\t\treturn new $s->{name}(frame.allocateArray(LAYOUT, count));\n"; + print $f "\t}\n\n"; + } + + print $f "\tpublic static $s->{name} create(ResourceScope scope) {\n"; + print $f "\t\treturn new $s->{name}(MemorySegment.allocateNative(LAYOUT, scope));\n"; + print $f "\t}\n\n"; + + if (!$isTyped) { + print $f "\tpublic static $s->{name} createArray(ResourceScope scope, long count) {\n"; + print $f "\t\treturn new $s->{name}(MemorySegment.allocateNative(LAYOUT.byteSize() * count, scope));\n"; + print $f "\t}\n\n"; + } + + # factory for all arguments for non-readonly types + # This should only be for typed types i think + # actually it should be always for typed types? + + # FIXME: embedded arrays are ignored and only accessed via getters + # ... but if they're primitive arrays they should probably be included as basically inlined + + #if ($isTyped && $#members > 1 && (!defined($accessMode{$s->{name}}) || ($accessMode{$s->{name}} & 2))) { + if (!defined($accessMode{$s->{name}}) || ($accessMode{$s->{name}} & 2)) { + # ignore next two here + my @range = @members[($isTyped ? 2 : 0) .. $#members]; + # analyse first TODO: put elsewhere + my $totalArgs = 0; + + foreach $m (@range) { + my $jtype = functionJavaType($m); + + # hmm, only if short? + if ($jtype eq 'String[]' && $m->{len} =~ m/([^,]+),?/) { + my $count = find($1, \@range); + + if (defined($count)) { + $count->{lenType} = 'implied-count'; + $m->{lenType} = 'implied'; + $m->{lenTarget} = $count->{name}; + } + } + + $m->{functionJavaType} = $jtype; + $m->{functionDescriptor} = functionDescriptorType($m); + + if ($m->{fullType} =~ m/\[(.+)\]/) { + my $prim = $typeToJavaPrimitive{$m->{baseType}}; + my $repeat = $1; + + $repeat = $apiConstants{$repeat} if defined $apiConstants{$repeat}; + + if ($prim && $repeat <= 9) { + $m->{shortPrimitiveArrayLength} = $repeat; + $m->{shortPrimitiveArrayType} = $prim; + $totalArgs += $repeat; + } + } elsif (!($m->{functionDescriptor} =~ m/\.LAYOUT$/)) { + $totalArgs += 1; + } + } + + # complex factories() + if ($totalArgs > 0 && $s->{category} eq 'struct') { + print $f "\tpublic static $s->{name} create(\n"; + print $f "\t\tFrame frame"; + foreach $m (@range) { + my $jtype = functionJavaType($m); + + next if ($m->{lenType} eq 'implied-count'); + # ignore embedded structs and handle arrays + next if ($m->{functionDescriptor} =~ m/\.LAYOUT$/); + + if ($m->{shortPrimitiveArrayLength}) { + for (my $i = 0;$i<$m->{shortPrimitiveArrayLength};$i++) { + print $f ",\n"; + print $f "\t\t$m->{shortPrimitiveArrayType} $m->{name}\$$i"; + } + } else { + print $f ",\n"; + print $f "\t\t$jtype $m->{name}"; + } + } + print $f ") {\n"; + print $f "\t\t$s->{name} self = create(frame);\n"; + + # Should probably just call set methods, but that's a pain to do here + foreach $m (@range) { + my $jtype = functionJavaType($m); + + next if ($m->{lenType} eq 'implied-count'); + # ignore embedded structs and arrays + next if ($m->{functionDescriptor} =~ m/\.LAYOUT$/); + next if ($m->{fullType} =~ m/\[.*\]/ && !$m->{shortPrimitiveArrayLength}); + + if ($jtype eq 'String' || $jtype eq 'String[]') { + print $f "\t\t$m->{name}\$VH.set(self.segment, frame.copy($m->{name}).address());\n"; + if ($m->{lenType} eq 'implied') { + print $f "\t\t$m->{lenTarget}\$VH.set(self.segment, $m->{name} != null ? $m->{name}.length : 0);\n"; + } + } elsif (defined($data{$jtype})) { + print $f "\t\t$m->{name}\$VH.set(self.segment, Memory.address($m->{name}));\n"; + } elsif ($m->{shortPrimitiveArrayLength}) { + print $f "$jtype $m->{name}\$array = self.get".ucfirst($m->{name})."();\n"; + for (my $i = 0;$i<$m->{shortPrimitiveArrayLength};$i++) { + print $f "$m->{name}\$array.setAtIndex($i, $m->{name}\$$i);\n"; + } + } elsif ($jtype =~ m/$Memory.*Array/) { + print $f "\t\t$m->{name}\$VH.set(self.segment, Memory.address($m->{name}));\n"; + } elsif ($jtype =~ m/^MemoryAddress|MemorySegment$/) { + # or maybe not, force the caller to use MemoryAddress.NULL? + print $f "\t\t$m->{name}\$VH.set(self.segment, Memory.address($m->{name}));\n"; + } else { + print $f "\t\t$m->{name}\$VH.set(self.segment, $m->{name});\n"; + } + } + print $f "\t\treturn self;\n"; + print $f "\t}\n"; + } + + if ($totalArgs > 0 && $s->{category} eq 'union') { + for $m (@range) { + next if ($m->{lenType} eq 'implied-count'); + # ignore embedded structs and handle arrays + next if ($m->{functionDescriptor} =~ m/\.LAYOUT$/); + next if ($m->{fullType} =~ m/\[.*\]/ && !$m->{shortPrimitiveArrayLength}); + + print $f "\tpublic static $s->{name} create".ucfirst($m->{name})."(\n"; + print $f "\t\tFrame frame"; + my $jtype = functionJavaType($m); + + if ($m->{shortPrimitiveArrayLength}) { + for (my $i = 0;$i<$m->{shortPrimitiveArrayLength};$i++) { + print $f ",\n"; + print $f "\t\t$m->{shortPrimitiveArrayType} $m->{name}\$$i"; + } + } else { + print $f ",\n"; + print $f "\t\t$jtype $m->{name}"; + } + + print $f ") {\n"; + print $f "\t\t$s->{name} self = create(frame);\n"; + + print $f "// $jtype\n"; + if ($jtype eq 'String' || $jtype eq 'String[]') { + print $f "\t\t$m->{name}\$VH.set(self.segment, frame.copy($m->{name}).address());\n"; + if ($m->{lenType} eq 'implied') { + print $f "\t\t$m->{lenTarget}\$VH.set(self.segment, $m->{name} != null ? $m->{name}.length : 0);\n"; + } + } elsif (defined($data{$jtype})) { + print $f "\t\t$m->{name}\$VH.set(self.segment, Memory.address($m->{name}));\n"; + } elsif ($m->{shortPrimitiveArrayLength}) { + print $f "$jtype array = self.get".ucfirst($m->{name})."();\n"; + for (my $i = 0;$i<$m->{shortPrimitiveArrayLength};$i++) { + print $f "array.setAtIndex($i, $m->{name}\$$i);\n"; + } + } elsif ($jtype =~ m/$Memory.*Array/) { + print $f "\t\t$m->{name}\$VH.set(self.segment, Memory.address($m->{name}));\n"; + } elsif ($jtype =~ m/^MemoryAddress|MemorySegment$/) { + # or maybe not, force the caller to use MemoryAddress.NULL? + print $f "\t\t$m->{name}\$VH.set(self.segment, Memory.address($m->{name}));\n"; + } else { + print $f "\t\t$m->{name}\$VH.set(self.segment, $m->{name});\n"; + } + print $f "\t\treturn self;\n"; + print $f "\t}\n"; + } + } + } + + + # accessors + my @range = $isTyped ? @members[1..$#members] : @members; + + # for arrays of structs, must obviously be sized properly + print $f "public long length() { return segment.byteSize() / LAYOUT.byteSize(); }\n"; + + foreach $m (@range) { + my $name = $m->{name}; + my $fullType = $m->{fullType}; + my $strip = $fullType =~ tr/*/*/; + + my $jtype = functionJavaType($m); + my $needFrame = $jtype =~ m/String/; + + # hack + if ($s->{name} eq 'VkAccelerationStructureBuildGeometryInfoKHR' + && $m->{name} eq 'pGeometries') { + $name = 'pGeometries0'; + } + + # cleanup name for setCamelCase() + $name = ucfirst(substr $name,$strip); + + # FIXME: MemoryAddress types + + if ((!defined($accessMode{$s->{name}}) || ($accessMode{$s->{name}} & 2)) + && !(functionDescriptorType($m) =~ m/\.LAYOUT$/ || $m->{fullType} =~ m/\[.*\]/)) { + # non-index + print $f "\tpublic void set$name("; + print $f "Frame frame, " if ($needFrame); + print $f "$jtype value) {\n"; + if ($jtype eq 'String' || $jtype eq 'String[]') { + print $f "\t\t$m->{name}\$VH.set(segment, frame.copy(value).address());\n"; + # auto-set length of a String[] + if ($jtype eq 'String[]' && $m->{len} =~ m/([^,]+),?/) { + my $src = $1; + print $f "\t\t$src\$VH.set(segment, value != null ? value.length : 0);\n"; + } + } elsif (defined($data{$jtype})) { + print $f "\t\t$m->{name}\$VH.set(segment, Memory.address(value));\n"; + } elsif ($jtype =~ m/$Memory.*Array/) { + print $f "\t\t$m->{name}\$VH.set(segment, Memory.address(value));\n"; + } else { + print $f "\t\t$m->{name}\$VH.set(segment, value);\n"; + } + print $f "\t}\n"; + + # indexed accessor + print $f "\tpublic void set$name("; + print $f "Frame frame, " if ($needFrame); + print $f "long index, $jtype value) {\n"; + print $f "\t\tMemorySegment seg = segment.asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize());\n"; + if ($jtype eq 'String' || $jtype eq 'String[]') { + print $f "\t\t$m->{name}\$VH.set(seg, frame.copy(value).address());\n"; + # auto-set length of a String[] + if ($jtype eq 'String[]' && $m->{len} =~ m/([^,]+),?/) { + my $src = $1; + print $f "\t\t$src\$VH.set(seg, value != null ? value.length : 0);\n"; + } + } elsif (defined($data{$jtype})) { + print $f "\t\t$m->{name}\$VH.set(segment, Memory.address(value));\n"; + } elsif ($jtype =~ m/$Memory.*Array/) { + print $f "\t\t$m->{name}\$VH.set(segment, Memory.address(value));\n"; + } else { + print $f "\t\t$m->{name}\$VH.set(segment, value);\n"; + } + print $f "\t}\n"; + } + + if (!defined($accessMode{$s->{name}}) || ($accessMode{$s->{name}} & 1) || $m->{fullType} =~ m/\[.*\]/ || functionDescriptorType($m) =~ m/\.LAYOUT$/) { + # non-index + print $f "\tpublic $jtype get$name() {\n"; + if ($jtype eq 'String') { + print $f "\t\tMemoryAddress ptr = (MemoryAddress)$m->{name}\$VH.get(segment);\n"; + print $f "\t\treturn ptr.getUtf8String(0);\n"; + } elsif ($jtype eq 'String[]') { + die("not implemented"); + } elsif (functionDescriptorType($m) =~ m/\.LAYOUT$/) { + print $f "\t\tMemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + print $f "\t\treturn new $jtype(segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize()));\n"; + } elsif ($m->{fullType} =~ m/\[.+\]/) { + print $f "\t\tMemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + print $f "\t\tMemorySegment seg = segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize());\n"; + if ($jtype =~ m/^Memory.HandleArray/) { + print $f "\t\treturn new $jtype($m->{baseType}::new, seg);\n"; + } else { + print $f "\t\treturn new $jtype(seg);\n"; + } + } else { + print $f "\t\treturn ($jtype)$m->{name}\$VH.get(segment);\n"; + } + print $f "\t}\n"; + + # indexed accessor FIXME: just run above parameterised with slice offset + # alternative something like: + # VarHandle getFlagsAtIndex = MemoryLayout.sequenceLayout(VkQueueFamilyProperties.LAYOUT) + # .varHandle(PathElement.sequenceElement(), PathElement.groupElement("queueFlags")); + # int flags = (int)getFlagsAtIndex.get(famprops.address(), (long)j); + + print $f "\tpublic $jtype get$name(long index) {\n"; + if ($jtype eq 'String') { + # FIXME: This looks ... really wrong + print $f "\t\tMemoryAddress ptr = (MemoryAddress)$m->{name}\$VH.get(segment.asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize()));\n"; + print $f "\t\treturn ptr.getUtf8String(0);\n"; + } elsif ($jtype eq 'String[]') { + die("not implemented"); + } elsif (functionDescriptorType($m) =~ m/\.LAYOUT$/) { + print $f "\t\tMemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + print $f "\t\treturn new $jtype(segment.asSlice(index * LAYOUT.byteSize() + LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize()));\n"; + } elsif ($m->{fullType} =~ m/\[.+\]/) { + print $f "\t\tMemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement(\"$m->{name}\");\n"; + print $f "\t\tMemorySegment seg = segment.asSlice(index * LAYOUT.byteSize() + LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize());\n"; + if ($jtype =~ m/^Memory.HandleArray/) { + print $f "\t\treturn new $jtype($m->{baseType}::new, seg);\n"; + } else { + print $f "\t\treturn new $jtype(seg);\n"; + } + } else { + print $f "\t\treturn ($jtype)$m->{name}\$VH.get(segment.asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize()));\n"; + } + print $f "\t}\n"; + } + } + + # insert template parts + if (open(my $template, "<", "template/$s->{name}-part.java")) { + print $f "// << inserted from: 'template/$s->{name}-part.java'\n"; + while (<$template>) { + print $f $_; + } + close ($template); + } + + # layout + foreach $m (@members) { + print $f "\t// $m->{fullType} $m->{name}\n"; + } + + print $f "\tpublic static final GroupLayout LAYOUT = "; + print $f formatLayout($s); + print $f ";\n"; + # varhandles + foreach $m (@members) { + # embedded structs are handled as offsets in the accessors + if (functionDescriptorType($m) =~ m/\.LAYOUT$/) { + print $f "\t// static final VarHandle $m->{name}\$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n"; + } else { + print $f "\tstatic final VarHandle $m->{name}\$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n"; + } + } + + print $f "}\n"; + close($f); + } elsif ($s->{category} eq "enum" || $s->{category} eq 'enum:bitmask') { + my @members = @{$s->{members}}; + my $type = calcSize($s) == 32 ? "int" : "long"; + my $L = $type eq "long" ? "L" : ""; + my %seen = (); + + open(my $f, ">", "$baseDir/$s->{name}.java") || die("unable to open $baseDir/$s->{name}.java"); + + print $f "package $targetPackage;\n"; + print $f "public class $s->{name} {\n"; + + my $tries = 0; + do { + my @later = (); + foreach $m (@members) { + next if ($seen{$m->{name}}); + + if (defined($m->{value})) { + print $f "\t/**\n\t * $m->{comment}\n\t*/\n" if ($m->{comment}); + print $f "\tpublic static final $type $m->{name} = $m->{value}$L;\n"; + $seen{$m->{name}} = $m; + } elsif (defined($m->{alias})) { + if ($seen{$m->{alias}}) { + print $f "\t/**\n\t * $m->{comment}\n\t*/\n" if ($m->{comment}); + print $f "\tpublic static final $type $m->{name} = $m->{alias};\n"; + $seen{$m->{name}} = $m; + } else { + push @later, $m; + } + } else { + print Dumper($s); + print Dumper($m); + die(); + } + } + @members = (@later); + } while ($#members >= 0 && $tries++ < 5); + + if ($#members >= 0) { + print Dumper($s); + print "Left over\n"; + foreach $m (@members) { + print Dumper($m); + } + print "seen\n"; + foreach $m (keys %seen) { + print Dumper($seen{$m}); + } + die("unable to resolve aliases"); + } + + # FIXME: this shouldn't be necessary, the other generators should just use the right primitive types + if ($type eq "int") { + print $f "final static jdk.incubator.foreign.ValueLayout.OfInt LAYOUT = Memory.INT;\n"; + } else { + print $f "final static jdk.incubator.foreign.ValueLayout.OfLong LAYOUT = Memory.LONG;\n"; + } + + print $f "}\n"; + close($f); + + } elsif ($s->{category} eq 'handle') { + open(my $f, ">", "$baseDir/$s->{name}.java") || die("unable to open $baseDir/$s->{name}.java"); + + print $f "package $targetPackage;\n"; + + print $f "import jdk.incubator.foreign.*;\n"; + print $f "import java.lang.invoke.*;\n"; + + print $f "public final class $s->{name} implements Memory.Addressable {\n"; + + print $f "\tfinal MemoryAddress address;\n"; + + # instances use a different constructor + if ($s->{type} eq 'VK_DEFINE_HANDLE') { + print $f "\tfinal DispatchInstance instanceDispatch;\n"; + if ($s->{name} eq "VkInstance") { + print $f "\t$s->{name}(MemoryAddress address) {\n"; + print $f "\t\tthis.address = address;\n"; + print $f "\t\tthis.instanceDispatch = new DispatchInstance(this, Memory.sharedScope());\n"; + print $f "\t}\n"; + + print $f "\tpublic static $s->{name} create(MemoryAddress address) {\n"; + print $f "\t\treturn address != MemoryAddress.NULL ? new $s->{name}(address) : null;\n"; + print $f "\t}\n\n"; + } elsif ($s->{name} =~ m/^VkPhysicalDevice/) { + print $f "\t$s->{name}(MemoryAddress address, DispatchInstance instanceDispatch) {\n"; + print $f "\t\tthis.address = address;\n"; + print $f "\t\tthis.instanceDispatch = instanceDispatch;\n"; + print $f "\t}\n"; + + print $f "\tpublic static $s->{name} create(MemoryAddress address, DispatchInstance instanceDispatch) {\n"; + print $f "\t\treturn address != MemoryAddress.NULL ? new $s->{name}(address, instanceDispatch) : null;\n"; + print $f "\t}\n\n"; + } elsif ($s->{name} =~ m/^VkDevice/) { + print $f "\tfinal DispatchDevice deviceDispatch;\n"; + print $f "\t$s->{name}(MemoryAddress address, DispatchInstance instanceDispatch) {\n"; + print $f "\t\tthis.address = address;\n"; + print $f "\t\tthis.instanceDispatch = instanceDispatch;\n"; + print $f "\t\tthis.deviceDispatch = new DispatchDevice(this, Memory.sharedScope());\n"; + print $f "\t}\n"; + + print $f "\tpublic static $s->{name} create(MemoryAddress address, DispatchInstance instanceDispatch) {\n"; + print $f "\t\treturn address != MemoryAddress.NULL ? new $s->{name}(address, instanceDispatch) : null;\n"; + print $f "\t}\n\n"; + } else { + print $f "\tfinal DispatchDevice deviceDispatch;\n"; + print $f "\t$s->{name}(MemoryAddress address, DispatchInstance instanceDispatch, DispatchDevice deviceDispatch) {\n"; + print $f "\t\tthis.address = address;\n"; + print $f "\t\tthis.instanceDispatch = instanceDispatch;\n"; + print $f "\t\tthis.deviceDispatch = deviceDispatch;\n"; + print $f "\t}\n"; + + print $f "\tpublic static $s->{name} create(MemoryAddress address, DispatchInstance instanceDispatch, DispatchDevice deviceDispatch) {\n"; + print $f "\t\treturn address != MemoryAddress.NULL ? new $s->{name}(address, instanceDispatch, deviceDispatch) : null;\n"; + print $f "\t}\n\n"; + } + } else { + print $f "\t$s->{name}(MemoryAddress address) {\n"; + print $f "\t\tthis.address = address;\n"; + print $f "\t}\n"; + + print $f "\tpublic static $s->{name} create(MemoryAddress address) {\n"; + print $f "\t\treturn address != MemoryAddress.NULL ? new $s->{name}(address) : null;\n"; + print $f "\t}\n\n"; + + print $f "\tpublic static Memory.HandleArray<$s->{name}> createArray(Frame frame, long count) {\n"; + print $f "\t\treturn new Memory.HandleArray<>(frame, $s->{name}::new, count);\n"; + print $f "\t}\n\n"; + + print $f "\tpublic static Memory.HandleArray<$s->{name}> createArray(ResourceScope scope, long count) {\n"; + print $f "\t\treturn new Memory.HandleArray<>($s->{name}::new, MemorySegment.allocateNative(Memory.POINTER.byteSize() * count, Memory.POINTER.byteAlignment(), scope));\n"; + print $f "\t}\n\n"; + } + + print $f "\tpublic final MemoryAddress address() { return address; }\n"; + + # add all methods who take this as the first argument + if ($s->{type} eq 'VK_DEFINE_HANDLE') { + print "Handle: $s->{name}\n"; + + foreach $c (@{$functionSets{$s->{name}}}) { + next if ($alias{$c} || $toDrop{$c}); + + my $cmd = $commands{$c}; + my $desc = formatFunctionDescriptor($cmd); + + $desc =~ s/^/\t\t/mg; + #print "// MethodHandle $cmd->{name}\n"; + #print Dumper($cmd); + + if (!defined $cmd->{extensionType}) { + print $f "\tstatic final MethodHandle $cmd->{name}\$FH = Memory.downcall(\n\t\t\"$cmd->{name}\",\n"; + print $f $desc; + print $f ");\n"; + } + + print $f "\t/**\n\t * $cmd->{proto}->{fullType} $cmd->{name} ("; + foreach $m (@{$cmd->{params}}) { + print $f " $m->{fullType}"; + } + print $f " )\n"; + print $f "\t * Requires $cmd->{extensionSource} extension \"$cmd->{extensionName}\"\n" if ($cmd->{extensionType}); + print $f "\t * Success Codes: $cmd->{successcodes}\n", if $cmd->{successcodes}; + print $f "\t * Error Codes: $cmd->{errorcodes}\n", if $cmd->{errorcodes}; + print $f "\t */\n"; + + if ($functionCreate{$c}) { + - exportCreateFunction($f, $cmd, 0) + } else { + exportFunction($f, $cmd, 0); + } + + # if ($c =~ m/^vkCreateDevice|vkGetDeviceQueue$/) { + # exportCreateFunction($f, $cmd, 0); + # } + } + + # and dump the static functions here too + if ($s->{name} eq "VkInstance") { + foreach $c (@{$functionSets{static}}) { + next if ($alias{$c} || $toDrop{$c}); + + my $cmd = $commands{$c}; + my $desc = formatFunctionDescriptor($cmd); + + $desc =~ s/^/\t\t/mg; + + #print "// static MethodHandle $cmd->{name}\n"; + #print Dumper($cmd); + + print $f "\t// $cmd->{proto}->{fullType} $cmd->{name} ("; + foreach $m (@{$cmd->{params}}) { + print $f " $m->{fullType}"; + } + print $f " )\n"; + + print $f "\tstatic final MethodHandle $cmd->{name}\$FH = Memory.downcall(\n\t\t\"$cmd->{name}\",\n"; + print $f $desc; + print $f ");\n"; + + # FIXME: need to make these non-static/init at startup, or probably - depending on extensions requested + my $d = $dump{$cmd->{name}}; + if ($d->{extension}) { + print $f "\t// EXTENSION $d->{extension}->{name}\n"; + } + + if ($functionCreate{$c}) { + exportCreateFunction($f, $cmd, 1); + } else { + exportFunction($f, $cmd, 1); + } + # if ($c eq 'vkCreateInstance') { + # exportCreateFunction($f, $cmd, 1); + # } + } + } + + if (open(my $template, "<", "template/$s->{name}-part.java")) { + print $f "// << inserted from: 'template/$s->{name}-part.java'\n"; + while (<$template>) { + print $f $_; + } + close ($template); + } + } + + print $f "}\n"; + close($f); + } else { + #print "X $s->{category}\n"; + } + +} + +# ###################################################################### # +# create extension handles +my %byExtension = (); +my %byType = (); +my %byHandle = (); +foreach $y (keys %functionSets) { + my $x = $functionSets{$y}; + foreach $c (@{$x}) { + next if ($alias{$c}); + my $cmd = $commands{$c}; + + my $d = $dump{$cmd->{name}}; + my $ext = $d->{extension}; + if (defined $ext) { + push @{$byExtension{$ext->{name}}}, $c; + push @{$byType{$ext->{type}}}, $c; + push @{$byHandle{$y}}, "$ext->{type} $c"; + } + } +} + +if (0) { + foreach $ext (sort keys %byHandle) { + print "extension $ext\n"; + foreach $c (sort @{$byHandle{$ext}}) { + print " $c\n"; + } + } +} + +foreach $ext (sort keys %functionByExtensionType) { + my @list = @{$functionByExtensionType{$ext}}; + my $className = 'Dispatch'.ucfirst($ext); + my $typeName = ucfirst($ext); + + open(my $f, ">", "$baseDir/$className.java") || die("unable to open $baseDir/$s->{name}.java"); + + print $f "package $targetPackage;\n"; + + print $f "import jdk.incubator.foreign.*;\n"; + print $f "import java.lang.invoke.*;\n"; + + print $f "final class $className {\n"; + + my @inits = (); + my $index = 'a'; + + foreach $c (sort @list) { + my $cmd = $commands{$c}; + + print $f " final MethodHandle $cmd->{name}\$FH;\n"; + } + + my $count = 0; + + print $f " $className(Vk$typeName o, ResourceScope scope) {\n"; + + foreach $c (sort @list) { + my $cmd = $commands{$c}; + my $desc = formatFunctionDescriptor($cmd); + + # if ($count == 0) { + # print $f " private void init_$index($typeName o, ResourceScope scope) {\n"; + # push @inits,"init_$index"; + # $index++; + # } + print $f " $cmd->{name}\$FH = Memory.downcall(\n"; + print $f " \"$cmd->{name}\",\n"; + print $f " o.vkGet$typeName"."ProcAddr(\"$cmd->{name}\"),\n"; + print $f $desc; + print $f ",\n scope);\n"; + + # if ($count++ == 100) { + # print $f " }\n"; + # $count = 0; + # } + } + + #print $f " }\n" if $count; + + # foreach $init (@inits) { + # print $f " $init(o, scope);\n"; + # } + print $f " }\n"; + + print $f "}\n"; +} + +if (0) { + print "unexported functions:\n"; + foreach $x (values %functionSets) { + foreach $y (@{$x}) { + print " $y alias=$alias{$y}\n" if !$commandsExported{$y}; + } + } +} +exit 0; + +# functions tst +foreach $x (sort keys %dump) { + my $d = $dump{$x}; + my $name = $d->{name}; + + # use this to check if it's in an alias function, just call the real one + #$name = $alias{$name} if (defined $alias{$name}); + + if ($d->{category} eq "command") { + my $cmd = $commands{$name}; + + if (defined($cmd)) { + #print Dumper($d); + #print Dumper($cmd); + if (0) { + print "\tstatic final MethodHandle $cmd->{name}\$FH = NativeZ.downcall(\n\t\t\"$cmd->{name}\",\n\t\t"; + dumpFunctionDescriptor($cmd); + print ");\n"; + } + print "\tpublic static "; + dumpFunctionPrototype($cmd); + print " {}\n"; + print "\n"; + } + } +} diff --git a/test-vulkan/mandelbrot.comp b/test-vulkan/mandelbrot.comp new file mode 100644 index 0000000..86e2a85 --- /dev/null +++ b/test-vulkan/mandelbrot.comp @@ -0,0 +1,64 @@ +#version 450 + +#define WIDTH (1920*1) +#define HEIGHT (1080*1) +#define LWS_X 8 +#define LWS_Y 8 +#define LIMIT 10000 + +layout (local_size_x = LWS_X, local_size_y = LWS_Y, local_size_z = 1 ) in; + +struct Pixel{ + vec4 value; +}; + +layout(std140, binding = 0) buffer buf +{ + Pixel imageData[]; +}; + +void main() { + + /* + In order to fit the work into workgroups, some unnecessary threads are launched. + We terminate those threads here. + */ + if(gl_GlobalInvocationID.x >= WIDTH || gl_GlobalInvocationID.y >= HEIGHT) + return; + + float x = float(gl_GlobalInvocationID.x) / float(WIDTH); + float y = float(gl_GlobalInvocationID.y) / float(HEIGHT); + + /* + What follows is code for rendering the mandelbrot set. + */ + vec2 uv = vec2(x, (y - 0.5) * (12.0 / 19.0) + 0.5); + float n = 0.0; + vec2 c = vec2(-.445, 0.0) + (uv - 0.5)*(4.0); + vec2 z = vec2(0.0); + const int M = LIMIT; + + for (int i = 0; i 4) + break; + n++; + } + + // we use a simple cosine palette to determine color: + // http://iquilezles.org/www/articles/palettes/palettes.htm + float t = float(n) * 500.0 / float(M); + vec3 d = vec3(0.5, 0.5, 0.5); + vec3 e = vec3(0.5, 0.5, 0.5); + vec3 f = vec3(1.0, 1.0, 1.0); + vec3 g = vec3(0.00, 0.33, 0.67); + + vec4 color = vec4( d + e*cos( 6.28318*(f*t+g) ) ,1.0); + + if (n == M) + color = vec4(0, 0, 0, 1); + + // store the rendered mandelbrot set into a storage buffer: + imageData[WIDTH * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x].value = color; +} diff --git a/test-vulkan/src/zvk/Frame.java b/test-vulkan/src/zvk/Frame.java new file mode 100644 index 0000000..0514252 --- /dev/null +++ b/test-vulkan/src/zvk/Frame.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package zvk; + +import jdk.incubator.foreign.*; +import static jdk.incubator.foreign.ValueLayout.OfAddress; + +public interface Frame extends AutoCloseable, SegmentAllocator { + + @Override + MemorySegment allocate(long size, long alignment); + + @Override + public void close(); + + default MemorySegment allocateInt() { + return allocate(Memory.INT); + } + + default MemorySegment allocateInt(int count) { + return allocate(Memory.INT, count); + } + + default MemorySegment allocateLong() { + return allocate(Memory.LONG); + } + + default MemorySegment allocateLong(int count) { + return allocateArray(Memory.LONG, count); + } + + default MemorySegment allocatePointer() { + return allocate(Memory.POINTER); + } + + default MemorySegment allocatePointer(int count) { + return allocateArray(Memory.POINTER, count); + } + + default MemorySegment allocateArray(OfAddress type, MemoryAddress[] value) { + MemorySegment m = allocateArray(type, value.length); + for (int i=0;i MemorySegment copy(T[] array) { + MemorySegment mem = allocateAddress(array.length); + for (int i = 0; i < array.length; i++) + MemoryAccess.setAddressAtIndex(mem, i, array[i].address()); + return mem; + } + + default MemorySegment copy(T value) { + return copy(value.address()); + } + + default MemorySegment copy(MemoryAddress value) { + MemorySegment mem = allocateAddress(); + MemoryAccess.setAddress(mem, value); + return mem; + } + */ + // create an array pointing to strings + default MemorySegment copy(String[] array) { + if (array != null) { + MemorySegment list = allocatePointer(array.length); + for (int i = 0; i < array.length; i++) { + list.setAtIndex(Memory.POINTER, i, copy(array[i])); + } + return list; + } else { + return Memory.NULL; + } + } + +} diff --git a/test-vulkan/src/zvk/Memory.java b/test-vulkan/src/zvk/Memory.java new file mode 100644 index 0000000..ac24cdb --- /dev/null +++ b/test-vulkan/src/zvk/Memory.java @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2020 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package zvk; + +import java.lang.invoke.*; +import java.lang.ref.Cleaner; +import jdk.incubator.foreign.*; +import static jdk.incubator.foreign.ValueLayout.*; + +import java.util.AbstractList; +import java.util.function.Function; + +/** + * A utility for memory operations including a stack allocator. + *

+ * The stack allocator works like this + *

+ * try (Frame f = Memory.createFrame()) {
+ *		MemorySegment a = f.allocate(size);
+ * }
+ * 
+ * Any memory allocated is freed when the frame is closed. + *

+ * This is MUCH faster than using MemorySegment.allocateNative(). + */ +public class Memory { + + // probably should be INT8 INT16, etc + public static final OfByte BYTE = JAVA_BYTE; + public static final OfShort SHORT = JAVA_SHORT.withBitAlignment(16); + public static final OfInt INT = JAVA_INT.withBitAlignment(32); + public static final OfLong LONG = JAVA_LONG.withBitAlignment(64); + public static final OfFloat FLOAT = JAVA_FLOAT.withBitAlignment(32); + public static final OfDouble DOUBLE = JAVA_DOUBLE.withBitAlignment(64); + public static final OfAddress POINTER = ADDRESS.withBitAlignment(64); + + static final ResourceScope sharedScope = ResourceScope.newSharedScope(); // cleaner? + static final MemorySegment NULL = MemorySegment.ofAddress(MemoryAddress.NULL, 1, ResourceScope.globalScope()); + + public static ResourceScope sharedScope() { + return sharedScope; + } + + public static MethodHandle downcall(String name, FunctionDescriptor desc) { + return SymbolLookup.loaderLookup().lookup(name) + .map(sym -> CLinker.systemCLinker().downcallHandle(sym, desc)) + .orElse(null); + } + + public static MethodHandle downcall(NativeSymbol sym, FunctionDescriptor desc) { + return CLinker.systemCLinker().downcallHandle(sym, desc); + } + + public static MethodHandle downcall(String name, MemoryAddress sym, FunctionDescriptor desc, ResourceScope scope) { + return sym != MemoryAddress.NULL + ? CLinker.systemCLinker().downcallHandle(NativeSymbol.ofAddress(name, sym, scope), desc) + : null; + } + + static final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + public static NativeSymbol upcall(Object instance, FunctionDescriptor desc, ResourceScope scope) { + try { + java.lang.reflect.Method m = instance.getClass().getMethods()[0]; + MethodHandle handle = lookup.findVirtual(instance.getClass(), "call", MethodType.methodType(m.getReturnType(), m.getParameterTypes())) + .bindTo(instance); + return CLinker.systemCLinker().upcallStub(handle, desc, scope); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + + + public static NativeSymbol upcall(Object instance, String method, String signature, FunctionDescriptor desc, ResourceScope scope) { + try { + MethodHandle handle = lookup.findVirtual(instance.getClass(), method, MethodType.fromMethodDescriptorString(signature, Memory.class.getClassLoader())) + .bindTo(instance); + return CLinker.systemCLinker().upcallStub(handle, desc, scope); + } catch (Throwable t) { + throw new AssertionError(t); + } + } + +static final ResourceScope scope = ResourceScope.newSharedScope(Cleaner.create()); + private static final ThreadLocal stacks = ThreadLocal.withInitial(() -> new Stack(scope)); + + public static Frame createFrame() { + return stacks.get().createFrame(); + } + + static class Stack { + + private final MemorySegment stack; + private long sp; + private Thread thread = Thread.currentThread(); + + Stack(ResourceScope scope) { + stack = MemorySegment.allocateNative(4096, 4096, scope); + sp = 4096; + } + + Frame createFrame() { + + return new Frame() { + private final long tos = sp; + private Thread self = thread; + private ResourceScope scope; + + @Override + public MemorySegment allocate(long size, long alignment) { + if (self != Thread.currentThread()) + throw new IllegalStateException(); + if (alignment != Long.highestOneBit(alignment)) + throw new IllegalArgumentException(); + if (sp >= size) { + sp = (sp - size) & ~(alignment - 1); + return stack.asSlice(sp, size).fill((byte)0); + } else { + if (scope == null) + scope = ResourceScope.newConfinedScope(); + return MemorySegment.allocateNative(size, alignment, scope); + } + } + + @Override + public void close() { + sp = tos; + self = null; + if (scope != null) { + scope.close(); + scope = null; + } + } + }; + } + } + + public static MemoryAddress address(jdk.incubator.foreign.Addressable v) { + return v != null ? v.address() : MemoryAddress.NULL; + } + + public static MemoryAddress address(Memory.Addressable v) { + return v != null ? v.address() : MemoryAddress.NULL; + } + + public interface Addressable { + MemoryAddress address(); + } + + // hmm do i want this or not? + // -> added 'type safety' + // -> load of crap to be written + public static class ByteArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public ByteArray(MemorySegment segment) { + this.segment = segment; + } + + public ByteArray(Frame frame, long size) { + this(frame.allocateArray(Memory.BYTE, size)); + } + + public ByteArray(Frame frame, byte... values) { + this(frame.allocateArray(Memory.BYTE, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Byte get(int index) { + return getAtIndex(index); + } + + @Override + public Byte set(int index, Byte value) { + byte old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.BYTE.byteSize(); + } + + public byte getAtIndex(long index) { + return (byte)segment.get(Memory.BYTE, index); + } + + public void setAtIndex(long index, byte value) { + segment.set(Memory.BYTE, index, value); + } + } + + public static class ShortArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public ShortArray(MemorySegment segment) { + this.segment = segment; + } + + public ShortArray(Frame frame, long size) { + this(frame.allocateArray(Memory.SHORT, size)); + } + + public ShortArray(Frame frame, short... values) { + this(frame.allocateArray(Memory.SHORT, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Short get(int index) { + return getAtIndex(index); + } + + @Override + public Short set(int index, Short value) { + short old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.SHORT.byteSize(); + } + + public short getAtIndex(long index) { + return segment.getAtIndex(Memory.SHORT, index); + } + + public void setAtIndex(long index, short value) { + segment.setAtIndex(Memory.SHORT, index, value); + } + } + + public static class IntArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public IntArray(MemorySegment segment) { + this.segment = segment; + } + + public IntArray(Frame frame, long size) { + this(frame.allocateArray(Memory.INT, size)); + } + + public IntArray(Frame frame, int... values) { + this(frame.allocateArray(Memory.INT, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Integer get(int index) { + return getAtIndex(index); + } + + @Override + public Integer set(int index, Integer value) { + int old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.INT.byteSize(); + } + + public int getAtIndex(long index) { + return segment.getAtIndex(Memory.INT, index); + } + + public void setAtIndex(long index, int value) { + segment.setAtIndex(Memory.INT, index, value); + } + } + + public static class LongArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public LongArray(MemorySegment segment) { + this.segment = segment; + } + + public LongArray(Frame frame, long size) { + this(frame.allocateArray(Memory.LONG, size)); + } + + public LongArray(Frame frame, long... values) { + this(frame.allocateArray(Memory.LONG, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Long get(int index) { + return getAtIndex(index); + } + + @Override + public Long set(int index, Long value) { + long old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.LONG.byteSize(); + } + + public long getAtIndex(long index) { + return segment.getAtIndex(Memory.LONG, index); + } + + public void setAtIndex(long index, long value) { + segment.setAtIndex(Memory.LONG, index, value); + } + } + + public static class FloatArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public FloatArray(MemorySegment segment) { + this.segment = segment; + } + + public FloatArray(Frame frame, long size) { + this(frame.allocateArray(Memory.FLOAT, size)); + } + + public FloatArray(Frame frame, float... values) { + this(frame.allocateArray(Memory.FLOAT, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Float get(int index) { + return getAtIndex(index); + } + + @Override + public Float set(int index, Float value) { + float old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.FLOAT.byteSize(); + } + + public float getAtIndex(long index) { + return segment.getAtIndex(Memory.FLOAT, index); + } + + public void setAtIndex(long index, float value) { + segment.setAtIndex(Memory.FLOAT, index, value); + } + } + + public static class DoubleArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public DoubleArray(MemorySegment segment) { + this.segment = segment; + } + + public DoubleArray(Frame frame, long size) { + this(frame.allocateArray(Memory.DOUBLE, size)); + } + + public DoubleArray(Frame frame, double... values) { + this(frame.allocateArray(Memory.DOUBLE, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public Double get(int index) { + return getAtIndex(index); + } + + @Override + public Double set(int index, Double value) { + double old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.DOUBLE.byteSize(); + } + + public double getAtIndex(long index) { + return segment.getAtIndex(Memory.DOUBLE, index); + } + + public void setAtIndex(long index, double value) { + segment.setAtIndex(Memory.DOUBLE, index, value); + } + } + + public static class PointerArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + + public PointerArray(MemorySegment segment) { + this.segment = segment; + } + + public PointerArray(Frame frame, long size) { + this(frame.allocateArray(Memory.POINTER, size)); + } + + public PointerArray(Frame frame, MemoryAddress... values) { + this(frame.allocateArray(Memory.POINTER, values)); + } + + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public MemoryAddress get(int index) { + return getAtIndex(index); + } + + @Override + public MemoryAddress set(int index, MemoryAddress value) { + MemoryAddress old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.POINTER.byteSize(); + } + + public MemoryAddress getAtIndex(long index) { + return segment.getAtIndex(Memory.POINTER, index); + } + + public void setAtIndex(long index, MemoryAddress value) { + segment.setAtIndex(Memory.POINTER, index, value); + } + } + + public static class HandleArray extends AbstractList implements Memory.Addressable { + final MemorySegment segment; + Function create; + + public HandleArray(Function create, MemorySegment segment) { + this.segment = segment; + this.create = create; + } + + public HandleArray(Frame frame, Function create, long size) { + this(create, frame.allocateArray(Memory.POINTER, size)); + } + + @Override + public final MemoryAddress address() { + return segment.address(); + } + + @Override + public int size() { + return (int)length(); + } + + @Override + public T get(int index) { + return getAtIndex(index); + } + + @Override + public T set(int index, T value) { + T old = getAtIndex(index); + setAtIndex(index, value); + return old; + } + + public long length() { + return segment.byteSize() / Memory.POINTER.byteSize(); + } + + public T getAtIndex(long index) { + MemoryAddress ptr = segment.getAtIndex(Memory.POINTER, index); + return ptr != null ? create.apply(ptr) : null; + } + + public void setAtIndex(long index, T value) { + segment.setAtIndex(Memory.POINTER, index, value != null ? value.address() : MemoryAddress.NULL); + } + } + +} diff --git a/test-vulkan/src/zvk/PFN_Test.java b/test-vulkan/src/zvk/PFN_Test.java new file mode 100644 index 0000000..d8327c5 --- /dev/null +++ b/test-vulkan/src/zvk/PFN_Test.java @@ -0,0 +1,107 @@ + +package zvk; + +import java.lang.invoke.*; +import jdk.incubator.foreign.*; + +/* +typedef void (*PFN_vkDeviceMemoryReportCallbackEXT)(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData, void* pUserData); +*/ + +// or just a record? does it need to do anything fancy? +/* +interface FunctionPointer { + NativeSymbol symbol(); + T function(); +} +*/ +record FunctionPointer(NativeSymbol symbol, T function) { } + +@FunctionalInterface +public interface PFN_Test extends Memory.Addressable { + + void call(VkDeviceMemoryReportCallbackDataEXT info); + + default MemoryAddress address() { + throw new UnsupportedOperationException("Non-native method"); + } + + @FunctionalInterface + interface Trampoline { + void call(MemoryAddress info, MemoryAddress userData); + }; + + static FunctionDescriptor DESCRIPTOR() { + return FunctionDescriptor.ofVoid( + Memory.POINTER, + Memory.POINTER); + } + + public static FunctionPointer upcall(PFN_Test target, ResourceScope scope) { + return new FunctionPointer<> ( + Memory.upcall((Trampoline)(info, userData)->{ + try { + target.call( + new VkDeviceMemoryReportCallbackDataEXT( + MemorySegment.ofAddress(info, + VkDeviceMemoryReportCallbackDataEXT.LAYOUT.byteSize(), + ResourceScope.globalScope()))); + } catch (Exception x) { + } + }, + "call", + "(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)I", + DESCRIPTOR(), + scope), + target); + } + + public static FunctionPointer downcall(String name, MemoryAddress addr, ResourceScope scope) { + NativeSymbol symbol = NativeSymbol.ofAddress(name, addr, scope); + MethodHandle fp = Memory.downcall(symbol, DESCRIPTOR()); + + return new FunctionPointer<>( + symbol, + (info) -> { + try { + fp.invokeExact((Addressable)Memory.address(info)); + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + + public static NativeSymbol ofUpcall(PFN_Test target, ResourceScope scope) { + Trampoline trampline = (info, userData) -> { + try { + target.call( + new VkDeviceMemoryReportCallbackDataEXT( + MemorySegment.ofAddress(info, + VkDeviceMemoryReportCallbackDataEXT.LAYOUT.byteSize(), + ResourceScope.globalScope()))); + } catch (Exception x) { + } + }; + return Memory.upcall(trampline, "call", + "(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)I", + DESCRIPTOR(), + scope); + } + + public static PFN_Test ofDowncall(String name, MemoryAddress addr, ResourceScope scope) { + NativeSymbol symbol = NativeSymbol.ofAddress(name, addr, scope); + MethodHandle fp = Memory.downcall(symbol, DESCRIPTOR()); + + return new PFN_Test() { + public MemoryAddress address() { return symbol.address(); }; + + public void call(VkDeviceMemoryReportCallbackDataEXT info) { + try { + fp.invokeExact((Addressable)Memory.address(info)); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }; + } +} diff --git a/test-vulkan/src/zvk/PFN_vkDebugReportCallbackEXT.java b/test-vulkan/src/zvk/PFN_vkDebugReportCallbackEXT.java new file mode 100644 index 0000000..226ea28 --- /dev/null +++ b/test-vulkan/src/zvk/PFN_vkDebugReportCallbackEXT.java @@ -0,0 +1,62 @@ + +package zvk; + +import java.lang.invoke.*; +import jdk.incubator.foreign.*; + +/* +callback: typedef VkBool32 (*PFN_vkDebugReportCallbackEXT)( + VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objectType, + uint64_t object, + size_t location, + int32_t messageCode, + const char* pLayerPrefix, + const char* pMessage, + void* pUserData); +*/ +public class PFN_vkDebugReportCallbackEXT implements Memory.Addressable { + + NativeSymbol symbol; + + public PFN_vkDebugReportCallbackEXT(NativeSymbol symbol) { + this.symbol = symbol; + } + + public MemoryAddress address() { + return symbol.address(); + } + + public interface Callback { + int call(int flags, int type, long thing, long location, int code, String layer, String message); + } + + private interface Trampoline { + int call(int flags, int type, long thing, long location, int code, MemoryAddress layer, MemoryAddress message, MemoryAddress userData); + } + + public static NativeSymbol of(Callback target, ResourceScope scope) { + Trampoline trampline = (int flags, int type, long thing, long location, int code, MemoryAddress layer, MemoryAddress message, MemoryAddress userData) -> { + try { + return target.call(flags, type, thing, location, code, + layer.getUtf8String(0), + message.getUtf8String(0)); + } catch (Exception x) { + } + return 0; + }; + return Memory.upcall(trampline, "call", + "(IIJJILjdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress)I", + FunctionDescriptor.of( + Memory.INT, + Memory.INT, + Memory.INT, + Memory.LONG, + Memory.LONG, + Memory.INT, + Memory.POINTER, + Memory.POINTER, + Memory.POINTER), + scope); + } +} diff --git a/test-vulkan/src/zvk/PFN_vkDebugUtilsMessengerCallbackEXT.java b/test-vulkan/src/zvk/PFN_vkDebugUtilsMessengerCallbackEXT.java new file mode 100644 index 0000000..873d694 --- /dev/null +++ b/test-vulkan/src/zvk/PFN_vkDebugUtilsMessengerCallbackEXT.java @@ -0,0 +1,49 @@ + +package zvk; + +import java.lang.invoke.*; +import jdk.incubator.foreign.*; + +public class PFN_vkDebugUtilsMessengerCallbackEXT implements Memory.Addressable { + + NativeSymbol symbol; + + public PFN_vkDebugUtilsMessengerCallbackEXT(NativeSymbol symbol) { + this.symbol = symbol; + } + + public MemoryAddress address() { + return symbol.address(); + } + + public interface Callback { + int call(int severity, int flags, VkDebugUtilsMessengerCallbackDataEXT data); + } + + private interface Trampoline { + int call(int severity, int flags, MemoryAddress addr, MemoryAddress userData); + } + + public static NativeSymbol of(Callback target, ResourceScope scope) { + Trampoline trampline = (severity, flags, addr, userData) -> { + try { + return target.call(severity, flags, + new VkDebugUtilsMessengerCallbackDataEXT( + MemorySegment.ofAddress(addr, + VkDebugUtilsMessengerCallbackDataEXT.LAYOUT.byteSize(), + ResourceScope.globalScope()))); + } catch (Exception x) { + } + return 0; + }; + return Memory.upcall(trampline, "call", + "(IILjdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)I", + FunctionDescriptor.of( + Memory.INT, + Memory.INT, + Memory.INT, + Memory.POINTER, + Memory.POINTER), + scope); + } +} diff --git a/test-vulkan/src/zvk/PFN_vkDeviceMemoryReportCallbackEXT.java b/test-vulkan/src/zvk/PFN_vkDeviceMemoryReportCallbackEXT.java new file mode 100644 index 0000000..dfda985 --- /dev/null +++ b/test-vulkan/src/zvk/PFN_vkDeviceMemoryReportCallbackEXT.java @@ -0,0 +1,48 @@ + +package zvk; + +import java.lang.invoke.*; +import jdk.incubator.foreign.*; + +/* +typedef void (*PFN_vkDeviceMemoryReportCallbackEXT)(const VkDeviceMemoryReportCallbackDataEXT* pCallbackData, void* pUserData); +*/ +public class PFN_vkDeviceMemoryReportCallbackEXT implements Memory.Addressable { + + NativeSymbol symbol; + + public PFN_vkDeviceMemoryReportCallbackEXT(NativeSymbol symbol) { + this.symbol = symbol; + } + + public MemoryAddress address() { + return symbol.address(); + } + + public interface Callback { + void call(VkDeviceMemoryReportCallbackDataEXT info); + } + + private interface Trampoline { + void call(MemoryAddress info, MemoryAddress userData); + } + + public static NativeSymbol of(Callback target, ResourceScope scope) { + Trampoline trampline = (info, userData) -> { + try { + target.call( + new VkDeviceMemoryReportCallbackDataEXT( + MemorySegment.ofAddress(info, + VkDeviceMemoryReportCallbackDataEXT.LAYOUT.byteSize(), + ResourceScope.globalScope()))); + } catch (Exception x) { + } + }; + return Memory.upcall(trampline, "call", + "(Ljdk/incubator/foreign/MemoryAddress;Ljdk/incubator/foreign/MemoryAddress;)I", + FunctionDescriptor.ofVoid( + Memory.POINTER, + Memory.POINTER), + scope); + } +} diff --git a/test-vulkan/src/zvk/VkPhysicalDeviceGroupProperties.java b/test-vulkan/src/zvk/VkPhysicalDeviceGroupProperties.java new file mode 100644 index 0000000..48bd7e6 --- /dev/null +++ b/test-vulkan/src/zvk/VkPhysicalDeviceGroupProperties.java @@ -0,0 +1,80 @@ +package zvk; +import jdk.incubator.foreign.*; +import java.lang.invoke.*; +public final class VkPhysicalDeviceGroupProperties implements Memory.Addressable { + final MemorySegment segment; + final DispatchInstance instanceDispatch; + + VkPhysicalDeviceGroupProperties(MemorySegment segment, DispatchInstance instanceDispatch) { + this.segment = segment; + this.instanceDispatch = instanceDispatch; + segment.set(Memory.INT, 0, VkStructureType.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES); + } + public final MemoryAddress address() { return segment.address(); } + /* + public static VkPhysicalDeviceGroupProperties create(Frame frame) { + return new VkPhysicalDeviceGroupProperties(frame.allocate(LAYOUT)); + } + + public static VkPhysicalDeviceGroupProperties create(ResourceScope scope) { + return new VkPhysicalDeviceGroupProperties(MemorySegment.allocateNative(LAYOUT, scope)); + }*/ + + // len= altLen= + MemoryAddress getNext() { + return (MemoryAddress)pNext$VH.get(segment); + } + MemoryAddress getNext(long index) { + return (MemoryAddress)pNext$VH.get(segment.asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize())); + } + + public long length() { + return segment.byteSize() / LAYOUT.byteSize(); + } + + // len= altLen= + int getPhysicalDeviceCount() { + return (int)physicalDeviceCount$VH.get(segment); + } + int getPhysicalDeviceCount(long index) { + return (int)physicalDeviceCount$VH.get(segment.asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize())); + } + // len= altLen= + Memory.HandleArray getPhysicalDevices() { + MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement("physicalDevices"); + MemorySegment seg = segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize()); + return new Memory.HandleArray((addr)->new VkPhysicalDevice(addr, instanceDispatch), seg); + } + Memory.HandleArray getPhysicalDevices(long index) { + MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement("physicalDevices"); + MemorySegment seg = segment.asSlice(index * LAYOUT.byteSize() + LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize()); + return new Memory.HandleArray((addr)->new VkPhysicalDevice(addr, instanceDispatch), seg); + } + // len= altLen= + int getSubsetAllocation() { + return (int)subsetAllocation$VH.get(segment); + } + int getSubsetAllocation(long index) { + return (int)subsetAllocation$VH.get(segment.asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize())); + } + // VkStructureType sType + // void* pNext + // uint32_t physicalDeviceCount + // VkPhysicalDevice [VK_MAX_DEVICE_GROUP_SIZE] physicalDevices + // VkBool32 subsetAllocation + public static final GroupLayout LAYOUT = MemoryLayout.structLayout( + Memory.INT.withName("sType"), + MemoryLayout.paddingLayout(32), + Memory.POINTER.withName("pNext"), + Memory.INT.withName("physicalDeviceCount"), + MemoryLayout.paddingLayout(32), + MemoryLayout.sequenceLayout(32, + Memory.POINTER).withName("physicalDevices"), + Memory.INT.withName("subsetAllocation"), + MemoryLayout.paddingLayout(32)); + static final VarHandle sType$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("sType")); + static final VarHandle pNext$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("pNext")); + static final VarHandle physicalDeviceCount$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("physicalDeviceCount")); + static final VarHandle physicalDevices$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("physicalDevices")); + static final VarHandle subsetAllocation$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("subsetAllocation")); +} diff --git a/test-vulkan/src/zvk/test/TestVulkan.java b/test-vulkan/src/zvk/test/TestVulkan.java new file mode 100644 index 0000000..cafef8c --- /dev/null +++ b/test-vulkan/src/zvk/test/TestVulkan.java @@ -0,0 +1,534 @@ + /* +The MIT License (MIT) + +Copyright (C) 2017 Eric Arnebäck +Copyright (C) 2019 Michael Zucchi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + */ + +/* + * This is a Java conversion of a C conversion of this: + * https://github.com/Erkaman/vulkan_minimal_compute + * + * It's been simplified a bit and converted to the 'zvk' api. + */ + +package zvk.test; + +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import java.lang.invoke.*; +import jdk.incubator.foreign.*; +import jdk.incubator.foreign.MemoryLayout.PathElement; + +import zvk.*; + +import static zvk.VkBufferUsageFlagBits.*; +import static zvk.VkMemoryPropertyFlagBits.*; +import static zvk.VkSharingMode.*; +import static zvk.VkDescriptorType.*; +import static zvk.VkShaderStageFlagBits.*; +import static zvk.VkCommandBufferLevel.*; +import static zvk.VkCommandBufferUsageFlagBits.*; +import static zvk.VkPipelineBindPoint.*; + +import static zvk.VkDebugUtilsMessageSeverityFlagBitsEXT.*; +import static zvk.VkDebugUtilsMessageTypeFlagBitsEXT.*; + +public class TestVulkan { + ResourceScope scope = ResourceScope.newSharedScope(); + + int WIDTH = 1920*1; + int HEIGHT = 1080*1; + + VkInstance instance; + VkPhysicalDevice physicalDevice; + + VkDevice device; + VkQueue computeQueue; + + long dstBufferSize = WIDTH * HEIGHT * 4 * 4; + //VkBuffer dstBuffer; + //VkDeviceMemory dstMemory; + BufferMemory dst; + + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorPool descriptorPool; + Memory.HandleArray descriptorSets = VkDescriptorSet.createArray(scope, 1); + + int computeQueueIndex; + VkPhysicalDeviceMemoryProperties deviceMemoryProperties; + + String mandelbrot_entry = "main"; + Memory.IntArray mandelbrot_cs; + + VkShaderModule mandelbrotShader; + VkPipelineLayout pipelineLayout; + Memory.HandleArray computePipeline = VkPipeline.createArray(scope, 1); + + VkCommandPool commandPool; + Memory.HandleArray commandBuffers; + + record BufferMemory ( VkBuffer buffer, VkDeviceMemory memory ) {}; + + VkDebugUtilsMessengerEXT logger; + + void init_debug() throws Exception { + try (Frame frame = Memory.createFrame()) { + NativeSymbol cb = PFN_vkDebugUtilsMessengerCallbackEXT.of((severity, flags, data) -> { + System.out.printf("Debug: %d: %s\n", severity, data.getMessage()); + return 0; + }, scope); + VkDebugUtilsMessengerCreateInfoEXT info = VkDebugUtilsMessengerCreateInfoEXT.create(frame, + 0, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + cb.address(), + null); + + logger = instance.vkCreateDebugUtilsMessengerEXT(info, null); + } + + //typedef VkBool32 (*PFN_vkDebugUtilsMessengerCallbackEXT)(VkDebugUtilsMessageSeverityFlagBitsEXT, VkDebugUtilsMessageTypeFlagsEXT, const VkDebugUtilsMessengerCallbackDataEXT *, void *); + + } + + void init_instance() throws Exception { + try (Frame frame = Memory.createFrame()) { + VkInstanceCreateInfo info = VkInstanceCreateInfo.create(frame, + 0, + VkApplicationInfo.create(frame, "test", 1, "test-engine", 2, VK_MAKE_API_VERSION(0, 1, 0, 0)), + new String[] { "VK_LAYER_KHRONOS_validation" }, + null //new String[] { "VK_EXT_debug_utils" } + ); + + instance = VkInstance.vkCreateInstance(info, null); + } + } + + void init_device() throws Exception { + try (Frame frame = Memory.createFrame()) { + Memory.IntArray count$h = new Memory.IntArray(frame, 1); + Memory.HandleArray devs; + int count; + int res; + + devs = instance.vkEnumeratePhysicalDevices(); + + int best = 0; + int devid = -1; + int queueid = -1; + + for (int i=0;i best) { + score = best; + devid = i; + queueid = j; + } + } + } + + if (devid == -1) + throw new Exception("Cannot find a suitable device"); + + computeQueueIndex = queueid; + physicalDevice = devs.getAtIndex(devid); + + Memory.FloatArray qpri = new Memory.FloatArray(frame, 0.0f); + VkDeviceQueueCreateInfo qinfo = VkDeviceQueueCreateInfo.create( + frame, + 0, + queueid, + 1, + qpri); + VkDeviceCreateInfo devinfo = VkDeviceCreateInfo.create( + frame, + 0, + 1, + qinfo, + null, + null, + null); + + device = physicalDevice.vkCreateDevice(devinfo, null); + + System.out.printf("device = %s\n", device.address()); + + // NOTE: app scope + deviceMemoryProperties = VkPhysicalDeviceMemoryProperties.create(scope); + physicalDevice.vkGetPhysicalDeviceMemoryProperties(deviceMemoryProperties); + + computeQueue = device.vkGetDeviceQueue(queueid, 0); + } + } + + /** + * Buffers are created in three steps: + * 1) create buffer, specifying usage and size + * 2) allocate memory based on memory requirements + * 3) bind memory + * + */ + BufferMemory init_buffer(long dataSize, int usage, int properties) throws Exception { + try (Frame frame = Memory.createFrame()) { + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + VkBufferCreateInfo buf_info = VkBufferCreateInfo.create(frame, + 0, + dataSize, + usage, + VK_SHARING_MODE_EXCLUSIVE, + 0, + null); + + VkBuffer buffer = device.vkCreateBuffer(buf_info, null); + + device.vkGetBufferMemoryRequirements(buffer, req); + + VkMemoryAllocateInfo alloc = VkMemoryAllocateInfo.create(frame, + req.getSize(), + find_memory_type(deviceMemoryProperties, req.getMemoryTypeBits(), properties)); + + VkDeviceMemory memory = device.vkAllocateMemory(alloc, null); + + device.vkBindBufferMemory(buffer, memory, 0); + + return new BufferMemory(buffer, memory); + } + } + + /** + * Descriptors are used to bind and describe memory blocks + * to shaders. + * + * *Pool is used to allocate descriptors, it is per-device. + * *Layout is used to group descriptors for a given pipeline, + * The descriptors describe individually-addressable blocks. + */ + void init_descriptor() throws Exception { + try (Frame frame = Memory.createFrame()) { + /* Create descriptorset layout */ + VkDescriptorSetLayoutBinding layout_binding = VkDescriptorSetLayoutBinding.create(frame, + 0, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + VK_SHADER_STAGE_COMPUTE_BIT, + null); + + VkDescriptorSetLayoutCreateInfo descriptor_layout = VkDescriptorSetLayoutCreateInfo.create(frame, + 0, + 1, + layout_binding); + + descriptorSetLayout = device.vkCreateDescriptorSetLayout(descriptor_layout, null); + + /* Create descriptor pool */ + VkDescriptorPoolSize type_count = VkDescriptorPoolSize.create(frame, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1); + + VkDescriptorPoolCreateInfo descriptor_pool = VkDescriptorPoolCreateInfo.create(frame, + 0, + 1, + 1, + type_count); + + descriptorPool = device.vkCreateDescriptorPool(descriptor_pool, null); + + /* Allocate from pool */ + Memory.HandleArray layout_table = VkDescriptorSetLayout.createArray(frame, 1); + + layout_table.setAtIndex(0, descriptorSetLayout); + + VkDescriptorSetAllocateInfo alloc_info = VkDescriptorSetAllocateInfo.create(frame, + descriptorPool, + 1, + layout_table); + + device.vkAllocateDescriptorSets(alloc_info, descriptorSets); + + /* Bind a buffer to the descriptor */ + VkDescriptorBufferInfo bufferInfo = VkDescriptorBufferInfo.create(frame, + dst.buffer, + 0, + dstBufferSize); + + VkWriteDescriptorSet writeSet = VkWriteDescriptorSet.create(frame, + descriptorSets.getAtIndex(0), + 0, + 0, + 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + null, + bufferInfo, + null); + + device.vkUpdateDescriptorSets(1, writeSet, 0, null); + } + } + + /** + * Create the compute pipeline. This is the shader and data layouts for it. + */ + void init_pipeline() throws Exception { + try (Frame frame = Memory.createFrame()) { + /* Set shader code */ + VkShaderModuleCreateInfo vsInfo = VkShaderModuleCreateInfo.create(frame, + 0, + mandelbrot_cs.length() * 4, + mandelbrot_cs); + + mandelbrotShader = device.vkCreateShaderModule(vsInfo, null); + + /* Link shader to layout */ + Memory.HandleArray layout_table = VkDescriptorSetLayout.createArray(frame, 1); + + layout_table.setAtIndex(0, descriptorSetLayout); + + VkPipelineLayoutCreateInfo pipelineinfo = VkPipelineLayoutCreateInfo.create(frame, + 0, + 1, + layout_table, + 0, + null); + + pipelineLayout = device.vkCreatePipelineLayout(pipelineinfo, null); + + /* Create pipeline */ + VkComputePipelineCreateInfo pipeline = VkComputePipelineCreateInfo.create(frame, + 0, + pipelineLayout, + null, + 0); + + VkPipelineShaderStageCreateInfo stage = pipeline.getStage(); + + stage.setStage(VK_SHADER_STAGE_COMPUTE_BIT); + stage.setModule(mandelbrotShader); + stage.setName(frame, mandelbrot_entry); + + device.vkCreateComputePipelines(null, 1, pipeline, null, computePipeline); + } + } + /** + * Create a command buffer, this is somewhat like a display list. + */ + void init_command_buffer() throws Exception { + try (Frame frame = Memory.createFrame()) { + VkCommandPoolCreateInfo poolinfo = VkCommandPoolCreateInfo.create(frame, + 0, + computeQueueIndex); + + commandPool = device.vkCreateCommandPool(poolinfo, null); + + VkCommandBufferAllocateInfo cmdinfo = VkCommandBufferAllocateInfo.create(frame, + commandPool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1); + + // should it take a scope? + commandBuffers = device.vkAllocateCommandBuffers(cmdinfo); + + /* Fill command buffer with commands for later operation */ + VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.create(frame, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + null); + + commandBuffers.get(0).vkBeginCommandBuffer(beginInfo); + + /* Bind the compute operation and data */ + commandBuffers.get(0).vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline.get(0)); + commandBuffers.get(0).vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, descriptorSets, 0, null); + + /* Run it */ + commandBuffers.get(0).vkCmdDispatch(WIDTH, HEIGHT, 1); + + commandBuffers.get(0).vkEndCommandBuffer(); + } + } + + /** + * Execute the pre-created command buffer. + * + * A fence is used to wait for completion. + */ + void execute() throws Exception { + try (Frame frame = Memory.createFrame()) { + VkSubmitInfo submitInfo = VkSubmitInfo.create(frame); + + submitInfo.setCommandBufferCount(0, 1); + submitInfo.setCommandBuffers(0, commandBuffers); + + /* Create fence to mark the task completion */ + VkFence fence; + Memory.HandleArray fences = VkFence.createArray(frame, 1); + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(frame); + + // maybe this should take a HandleArray rather than being a constructor + fence = device.vkCreateFence(fenceInfo, null); + fences.set(0, fence); + + /* Await completion */ + computeQueue.vkQueueSubmit(1, submitInfo, fence); + + int VK_TRUE = 1; + int res; + do { + res = device.vkWaitForFences(1, fences, VK_TRUE, 1000000); + } while (res == VkResult.VK_TIMEOUT); + + device.vkDestroyFence(fence, null); + } + } + + void shutdown() { + device.vkDestroyCommandPool(commandPool, null); + device.vkDestroyPipeline(computePipeline.getAtIndex(0), null); + device.vkDestroyPipelineLayout(pipelineLayout, null); + device.vkDestroyShaderModule(mandelbrotShader, null); + + device.vkDestroyDescriptorPool(descriptorPool, null); + device.vkDestroyDescriptorSetLayout(descriptorSetLayout, null); + + device.vkFreeMemory(dst.memory(), null); + device.vkDestroyBuffer(dst.buffer(), null); + + device.vkDestroyDevice(null); + instance.vkDestroyInstance(null); + } + + /** + * Accesses the gpu buffer, converts it to RGB byte, and saves it as a pam file. + */ + void save_result() throws Exception { + try (ResourceScope scope = ResourceScope.newConfinedScope()) { + MemorySegment mem = device.vkMapMemory(dst.memory(), 0, dstBufferSize, 0, scope); + byte[] pixels = new byte[WIDTH * HEIGHT * 3]; + + // this is super-slow! + for (int i = 0; i < WIDTH * HEIGHT; i++) { + pixels[i * 3 + 0] = (byte)(255.0f * mem.getAtIndex(Memory.FLOAT, i * 4 + 0)); + pixels[i * 3 + 1] = (byte)(255.0f * mem.getAtIndex(Memory.FLOAT, i * 4 + 1)); + pixels[i * 3 + 2] = (byte)(255.0f * mem.getAtIndex(Memory.FLOAT, i * 4 + 2)); + } + + device.vkUnmapMemory(dst.memory()); + + pam_save("mandelbrot.pam", WIDTH, HEIGHT, 3, pixels); + } + } + + + /** + * Trivial pnm format image output. + */ + void pam_save(String name, int width, int height, int depth, byte[] pixels) throws IOException { + try (FileOutputStream fos = new FileOutputStream(name)) { + fos.write(String.format("P6\n%d\n%d\n255\n", width, height).getBytes()); + fos.write(pixels); + System.out.printf("wrote: %s\n", name); + } + } + + static Memory.IntArray loadSPIRV(String name) throws IOException { + // hmm any way to just load this directly? + try (InputStream is = TestVulkan.class.getResourceAsStream(name)) { + ByteBuffer bb = ByteBuffer.allocateDirect(8192).order(ByteOrder.nativeOrder()); + int length = Channels.newChannel(is).read(bb); + + bb.position(0); + bb.limit(length); + return new Memory.IntArray(MemorySegment.ofByteBuffer(bb)); + } + } + + /** + * This finds the memory type index for the memory on a specific device. + */ + static int find_memory_type(VkPhysicalDeviceMemoryProperties memory, int typeMask, int query) { + VkMemoryType mtypes = memory.getMemoryTypes(); + + for (int i = 0; i < memory.getMemoryTypeCount(); i++) { + if (((1 << i) & typeMask) != 0 && ((mtypes.getPropertyFlags(i) & query) == query)) + return i; + } + return -1; + } + + public static int VK_MAKE_API_VERSION(int variant, int major, int minor, int patch) { + return (variant << 29) | (major << 22) | (minor << 12) | patch; + } + + void demo() throws Exception { + mandelbrot_cs = loadSPIRV("mandelbrot.bin"); + + init_instance(); + //init_debug(); + init_device(); + + dst = init_buffer(dstBufferSize, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + init_descriptor(); + init_pipeline(); + init_command_buffer(); + + System.out.printf("Calculating %dx%d\n", WIDTH, HEIGHT); + execute(); + System.out.println("Saving ..."); + save_result(); + System.out.println("Done."); + + shutdown(); + } + + + public static void main(String[] args) throws Throwable { + System.loadLibrary("vulkan"); + + new TestVulkan().demo(); + } +} diff --git a/test-vulkan/template/VkDevice-part.java b/test-vulkan/template/VkDevice-part.java new file mode 100644 index 0000000..0de01d2 --- /dev/null +++ b/test-vulkan/template/VkDevice-part.java @@ -0,0 +1,62 @@ + +// cleaner? +ResourceScope deviceScope = ResourceScope.newSharedScope(); + + static final MethodHandle vkAllocateCommandBuffers$FH = Memory.downcall( + "vkAllocateCommandBuffers", + FunctionDescriptor.of( + Memory.INT, + Memory.POINTER.withName("device"), + Memory.POINTER.withName("pAllocateInfo"), + Memory.POINTER.withName("pCommandBuffers"))); + /** + * VkResult vkAllocateCommandBuffers ( VkDevice const VkCommandBufferAllocateInfo* VkCommandBuffer* ) + * Success Codes: VK_SUCCESS + * Error Codes: VK_ERROR_OUT_OF_HOST_MEMORY,VK_ERROR_OUT_OF_DEVICE_MEMORY + */ + public Memory.HandleArray vkAllocateCommandBuffers(VkCommandBufferAllocateInfo pAllocateInfo)throws Exception { + int res$code; + try { + Memory.HandleArray pCommandBuffers = + new Memory.HandleArray<>((addr)->new VkCommandBuffer(addr, instanceDispatch, deviceDispatch), + MemorySegment.allocateNative(pAllocateInfo.getCommandBufferCount() * Memory.POINTER.byteSize(), Memory.POINTER.bitAlignment(), deviceScope)); + + res$code = (int)vkAllocateCommandBuffers$FH.invokeExact( + (Addressable)address(), + (Addressable)Memory.address(pAllocateInfo), + (Addressable)Memory.address(pCommandBuffers)); + if (res$code == VkResult.VK_SUCCESS) return pCommandBuffers; + } catch (Throwable t) { throw new RuntimeException(t); } + throw new Exception(String.format("failcode %d", res$code)); + } + + static final MethodHandle vkMapMemory$FH = Memory.downcall( + "vkMapMemory", + FunctionDescriptor.of( + Memory.INT, + Memory.POINTER.withName("device"), + Memory.POINTER.withName("memory"), + Memory.LONG.withName("offset"), + Memory.LONG.withName("size"), + Memory.INT.withName("flags"), + Memory.POINTER.withName("ppData"))); + /** + * VkResult vkMapMemory ( VkDevice VkDeviceMemory VkDeviceSize VkDeviceSize VkMemoryMapFlags void** ) + * Success Codes: VK_SUCCESS + * Error Codes: VK_ERROR_OUT_OF_HOST_MEMORY,VK_ERROR_OUT_OF_DEVICE_MEMORY,VK_ERROR_MEMORY_MAP_FAILED + */ + public MemorySegment vkMapMemory(VkDeviceMemory memory, long offset, long size, int flags, ResourceScope scope)throws Exception { + int res$code; + try (Frame frame = Memory.createFrame()) { + MemorySegment ppData = frame.allocatePointer(); + res$code = (int)vkMapMemory$FH.invokeExact( + (Addressable)address(), + (Addressable)Memory.address(memory), + (long)offset, + (long)size, + (int)flags, + (Addressable)Memory.address(ppData)); + if (res$code == VkResult.VK_SUCCESS) return MemorySegment.ofAddress(ppData.getAtIndex(Memory.POINTER, 0), size, scope); + } catch (Throwable t) { throw new RuntimeException(t); } + throw new Exception(String.format("failcode %d", res$code)); + } diff --git a/test-vulkan/template/VkInstance-part.java b/test-vulkan/template/VkInstance-part.java new file mode 100644 index 0000000..dcd50e6 --- /dev/null +++ b/test-vulkan/template/VkInstance-part.java @@ -0,0 +1,79 @@ + +// cleaner? +ResourceScope instanceScope = ResourceScope.newSharedScope(); + + static final MethodHandle vkEnumeratePhysicalDevices$FH = Memory.downcall( + "vkEnumeratePhysicalDevices", + FunctionDescriptor.of( + Memory.INT, + Memory.POINTER.withName("instance"), + Memory.POINTER.withName("pPhysicalDeviceCount"), + Memory.POINTER.withName("pPhysicalDevices"))); + /** + * VkResult vkEnumeratePhysicalDevices ( VkInstance uint32_t* VkPhysicalDevice* ) + */ + public Memory.HandleArray vkEnumeratePhysicalDevices()throws Exception { + int res$code; + try (Frame frame = Memory.createFrame()) { + MemorySegment count = frame.allocateInt(); + + res$code = (int)vkEnumeratePhysicalDevices$FH.invokeExact( + (Addressable)address(), + (Addressable)count.address(), + (Addressable)MemoryAddress.NULL); + + if (res$code == VkResult.VK_SUCCESS) { + Memory.HandleArray devices = + new Memory.HandleArray<>((addr)->new VkPhysicalDevice(addr, instanceDispatch), + MemorySegment.allocateNative(count.get(Memory.INT, 0) * Memory.POINTER.byteSize(), Memory.POINTER.bitAlignment(), instanceScope)); + + res$code = (int)vkEnumeratePhysicalDevices$FH.invokeExact( + (Addressable)address(), + (Addressable)count.address(), + (Addressable)devices.address()); + + if (res$code == VkResult.VK_SUCCESS) + return devices; + } + } catch (Throwable t) { throw new RuntimeException(t); } + throw new Exception(String.format("failcode %d", res$code)); + } + + static final MethodHandle vkEnumeratePhysicalDeviceGroups$FH = Memory.downcall( + "vkEnumeratePhysicalDeviceGroups", + FunctionDescriptor.of( + Memory.INT, + Memory.POINTER.withName("instance"), + Memory.POINTER.withName("pPhysicalDeviceGroupCount"), + Memory.POINTER.withName("pPhysicalDeviceGroupProperties"))); + /** + * VkResult vkEnumeratePhysicalDeviceGroups ( VkInstance uint32_t* VkPhysicalDeviceGroupProperties* ) + */ + public VkPhysicalDeviceGroupProperties vkEnumeratePhysicalDeviceGroups() throws Exception { + int res$code; + try (Frame frame = Memory.createFrame()) { + MemorySegment count = frame.allocateInt(); + + res$code = (int)vkEnumeratePhysicalDeviceGroups$FH.invokeExact( + (Addressable)address(), + (Addressable)count.address(), + (Addressable)MemoryAddress.NULL); + + if (res$code == VkResult.VK_SUCCESS) { + VkPhysicalDeviceGroupProperties properties = + new VkPhysicalDeviceGroupProperties( + MemorySegment.allocateNative(count.get(Memory.INT, 0) * VkPhysicalDeviceGroupProperties.LAYOUT.byteSize(), 64, instanceScope), + instanceDispatch); + + res$code = (int)vkEnumeratePhysicalDeviceGroups$FH.invokeExact( + (Addressable)address(), + (Addressable)count.address(), + (Addressable)properties.address()); + + if (res$code == VkResult.VK_SUCCESS) + return properties; + } + + } catch (Throwable t) { throw new RuntimeException(t); } + throw new Exception(String.format("failcode %d", res$code)); + } -- 2.39.2