Wrote entirely new generator for c header files.
Added vulkan demo. Generates api from api specification.
Makefile tweaks.
https://blog.adacore.com/bindings-gcc-plugins
*/
+/*
+ TODO: get header name from tree
+ */
+
/*
function declarations, i think
#include <stdio.h>
#include <stdlib.h>
+#include <stdarg.h>
#include <string.h>
#include <gcc-plugin.h>
#include <tree.h>
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) {
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.
*/
}
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) {
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);
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: {
// 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);
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:
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;
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
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);
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
}
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
free(stack_pull(&context_stack));
- fprintf(output_file, "},\n");
+ generate("},\n");
id++;
}
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
free(stack_pull(&context_stack));
- fprintf(output_file, "},\n");
+ generate("},\n");
}
}
}
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
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: {
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
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");
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: {
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");
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
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: {
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:
// 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);
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; }
}
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]) {
}
}
- 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);
}
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);
+++ /dev/null
-#!/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 <<END;
-import java.foreign.Libraries;
-import java.foreign.annotations.*;
-import java.foreign.memory.*;
-import java.lang.invoke.MethodHandles;
-END
- print $dst "public class $class {\n";
-}
-
-# Dump structures
-for $k (findStructs(\%data, @matchStruct)) {
- my %struct = %{$data{$k}};
- my @fields = @{$struct{fields}};
- my $signature = structSignature(\%struct, ($struct{type} eq "union"));
- my $name = StudlyCaps($struct{name});
-
- 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 <<END;
-import java.foreign.annotations.*;
-import java.foreign.memory.*;
-END
- }
-
- # any in-line structures need to be added to the resolutionContext
- # TODO: only include actual inline, not pointers
- my %resolve = ();
- for $fi (@fields) {
- my %field = %{$fi};
-
- if ($field{type} =~ m/^(struct|union):(.*)/) {
- $resolve{StudlyCaps($2).".class"} = 1;
- }
- }
- my $resolve = join (",", keys %resolve);
-
- print $dst "\@NativeStruct(value=\"$signature($struct{name})\"";
- print $dst ", resolutionContext={$resolve}" if ($resolve);
- print $dst ")\n";
- print $dst "public interface $name extends Struct<$name> {\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 <<END;
-import java.foreign.Libraries;
-import java.foreign.annotations.*;
-import java.foreign.memory.*;
-import java.lang.invoke.MethodHandles;
-END
- }
-
- print $dst "\@NativeHeader(libraries={";
- print $dst join(",", map { "\"$_\"" } @libs);
- print $dst "})\n";
- print $dst "public interface $class{name} {\n";
-
- # enums to ints
- # TODO: interfaces?
- # TODO: static lib class?
- # typedef enums might appear twice in the data, so ignore duplicates
- # also, some api's have multiple definitions (?)
- my %visited = ();
- my @match_enum = @{$class{enum}};
- for $k (sort(findDefinition(\%data, 'enum', @match_enum))) {
- my %enum = %{$data{$k}};
- my @values = @{$enum{values}};
- my $type = "int";
-
- if ($enum{value_type} =~ m/^[ui](\d+)/) {
- $type = "long" if ($1 > 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 <<END;
-import java.foreign.Libraries;
-import java.foreign.annotations.*;
-import java.foreign.memory.*;
-END
- }
-
- # any in-line structures need to be added to the resolutionContext
- # TODO: only include actual inline, not pointers
- my %resolve = ();
- my @list = @params;
- unshift(@list,$call{result});
- for $pi (@list) {
- my %param = %{$pi};
-
- if ($param{type} =~ m/^(struct|union):(.*)/) {
- $resolve{StudlyCaps($2).".class"} = 1;
- }
- }
- my $resolve = join (",", keys %resolve);
-
- # FIXME: use something other than name to store this
- print $dst "\@FunctionalInterface\n";
- print $dst "\@NativeCallback(value=\"$call{name}\"";
- print $dst ", resolutionContext={$resolve}" if ($resolve);
- print $dst ")\n";
- print $dst "public interface $name {\n";
- print $dst "\tpublic $result fn(";
-
- 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";
-
- if (!$enclosingType) {
- close($dst);
- }
-}
-
-# Finish off
-if ($enclosingType) {
- print $dst "}\n";
- close($dst);
-}
+++ /dev/null
-#!/usr/bin/perl
-
-# replace a datatype with another, do not generate any code for it
-# -r name=new
-
-@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 = "";
-%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 <<END;
-import java.foreign.Libraries;
-import java.lang.invoke.MethodHandles;
-import jdk.incubator.foreign.*;
-import api.Native;
-$importPointer
-END
- print $dst "public class $class {\n";
-}
-
-# ######################################################################
-# This is work in progress, aka a total fucking mess
-# Dump structures
-for $k (findStructs(\%data, @matchStruct)) {
- my %struct = %{$data{$k}};
- my @fields = @{$struct{fields}};
- my $signature = structSignature(\%struct, ($struct{type} eq "union"));
- my $name = StudlyCaps($struct{name});
-
- 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 <<END;
-import jdk.incubator.foreign.*;
-import api.Native;
-$importPointer
-
-END
- }
-
- print $dst "public class $name extends Native {\n";
-
- print $dst "\tpublic $name(MemoryAddress p) {\n";
- print $dst "\t\tsuper(p);\n";
- print $dst "\t}\n";
-
- for $fi (@fields) {
- my %field = %{$fi};
- my $type = typeToJava(\%field);
- my $cc = StudlyCaps($field{name});
-
- if ($field{deref} =~ m/^\[/) {
- # array
- my %info = arrayInfo($field{deref});
- my @dims = @{$info{dims}};
- $info{type} = $field{type};
- my $atype = typeToJava(\%info);
- my @strides = ();
-
- my $stride = 1;
- for $dim (reverse(0 .. $#dims)) {
- push @strides,$stride;
- $stride *= $dims[$dim];
- }
-
- if ($field{type} =~ m/^(struct|union):(.*)/) {
- for $dim (0 .. $#dims) {
- print $dst "// $dims[$dim]\n";
- }
-
- print $dst "public $atype get$cc(";
- for $dim (0 .. $#dims) {
- print $dst ", " if ($dim != 0);
- print $dst "int i$dim";
- }
- print $dst ") {\n";
- print $dst "\tint i=";
- for $dim (0 .. $#dims) {
- print $dst " + " if ($dim != 0);
- print $dst "(i$dim * $strides[$#dims - $dim])";
- }
- print $dst ";\n";
- print $dst "return Native.Pointer.ofAddress(addr().addOffset(i * 8), 32, Data::new);\n";
- print $dst "}\n";
- } elsif ($field{type} =~ m/^call:/) {
- } else {
- }
- } elsif ($field{deref} =~ m/^u64:\$/) {
- # pointer-to-struct
- 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 $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 <<END;
-import jdk.incubator.foreign.*;
-import java.lang.invoke.MethodHandle;
-import api.Native;
-$importPointer
-END
- }
-
- print $dst "public class $class{name} {\n";
-
- print $dst "\tstatic final String[] libraries = {";
- print $dst join(",", map { "\"$_\"" } @libs);
- print $dst "};\n";
-
- # enums to ints
- # TODO: interfaces?
- # TODO: static lib class?
- # typedef enums might appear twice in the data, so ignore duplicates
- # also, some api's have multiple definitions (?)
- my %visited = ();
- my @match_enum = @{$class{enum}};
- for $k (sort(findDefinition(\%data, 'enum', @match_enum))) {
- my %enum = %{$data{$k}};
- my @values = @{$enum{values}};
- my $type = "int";
-
- if ($enum{value_type} =~ m/^[ui](\d+)/) {
- $type = "long" if ($1 > 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<Void>/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 <<END;
-import jdk.incubator.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.Method;
-import api.Callback;
-import api.Native;
-$importPointer
-END
- }
-
- print $dst "\@FunctionalInterface\n";
- print $dst "public interface $name {\n";
- print $dst "\tpublic $result fn(";
-
- 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";
-
- # downcall
- print $dst "\tstatic public $name of(MemoryAddress addr) {\n";
- print $dst "\t\tMethodHandle func = Native.downcallHandle(addr, \"$signature\");\n";
- print $dst "\t\treturn (";
- for $pi (@params) {
- my %param = %{$pi};
- my $type = typeToRaw($pi);
-
- print $dst "$type $param{name}";
- print $dst ", " if ($pi != $params[$#params]);
- }
- 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<Pointer<Void>>") {
- # 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 <<END;
-import jdk.incubator.foreign.*;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.Method;
-import api.Native;
-$importPointer
-END
- }
-
- # any in-line structures need to be added to the resolutionContext
- # TODO: only include actual inline, not pointers
- #my %resolve = ();
- #my @list = @params;
- #unshift(@list,$call{result});
- #for $pi (@list) {
- # my %param = %{$pi};
- #
- # if ($param{type} =~ m/^(struct|union):(.*)/) {
- # $resolve{StudlyCaps($2).".class"} = 1;
- # }
- # }
- #my $resolve = join (",", keys %resolve);
-
- print $dst "\@FunctionalInterface\n";
- print $dst "public interface $name {\n";
- print $dst "\tpublic $result fn(";
-
- for $pi (@params) {
- my %param = %{$pi};
- my $type = typeToJava($pi);
-
- print $dst "$type $param{name}";
- print $dst ", " if ($pi != $params[$#params]);
- }
-
- print $dst ");\n";
-
- # downcall
- print $dst "\tstatic public $name of(MemoryAddress addr) {\n";
- print $dst "\t\tMethodHandle func = Native.downcallHandle(addr, \"$signature\");\n";
- print $dst "\t\treturn (";
- 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 "\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<Pointer<Void>>") {
- 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);
-}
--- /dev/null
+#!/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;
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+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<value.length;i++)
+ m.setAtIndex(type, i, value[i]);
+ return m;
+ }
+
+ default MemorySegment copy(byte value) {
+ return allocate(Memory.BYTE, value);
+ }
+
+ default MemorySegment copy(short value) {
+ return allocate(Memory.SHORT, value);
+ }
+
+ default MemorySegment copy(int value) {
+ return allocate(Memory.INT, value);
+ }
+
+ default MemorySegment copy(long value) {
+ return allocate(Memory.LONG, value);
+ }
+
+ default MemorySegment copy(float value) {
+ return allocate(Memory.FLOAT, value);
+ }
+
+ default MemorySegment copy(double value) {
+ return allocate(Memory.DOUBLE, value);
+ }
+
+ default MemorySegment copy(byte[] value) {
+ return allocateArray(Memory.BYTE, value);
+ }
+
+ default MemorySegment copy(int[] value) {
+ return allocateArray(Memory.INT, value);
+ }
+
+ default MemorySegment copy(long[] value) {
+ return allocateArray(Memory.LONG, value);
+ }
+
+ default MemorySegment copy(float[] value) {
+ return allocateArray(Memory.FLOAT, value);
+ }
+
+ default public MemorySegment copy(String value) {
+ return allocateUtf8String(value);
+ }
+
+ /*
+ default <T extends Native> 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 <T extends Native> MemorySegment copy(T value) {
+ return copy(value.address());
+ }
+
+ default <T extends Native> 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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.
+ * <p>
+ * The stack allocator works like this
+ * <pre>
+ * try (Frame f = Memory.createFrame()) {
+ * MemorySegment a = f.allocate(size);
+ * }
+ * </pre>
+ * Any memory allocated is freed when the frame is closed.
+ * <p>
+ * 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<Stack> 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<T>(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 <T> MemoryAddress address(FunctionPointer<T> 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<Byte> 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<Short> 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<Integer> 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<Long> 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<Float> 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<Double> 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<MemoryAddress> 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<T extends Memory.Addressable> extends AbstractList<T> implements Memory.Addressable {
+ final MemorySegment segment;
+ BiFunction<MemoryAddress,ResourceScope,T> create;
+
+ private HandleArray(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create) {
+ this.segment = segment;
+ this.create = create;
+ }
+
+ public static <T extends Memory.Addressable> HandleArray<T> create(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create) {
+ return new HandleArray<>(segment, create);
+ }
+
+ public static <T extends Memory.Addressable> HandleArray<T> createArray(long size, SegmentAllocator alloc, BiFunction<MemoryAddress,ResourceScope,T> 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+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.
+ * <p>
+ * Handles instantiation and provides helper functions for native access.
+ * <p>
+ * Work in progress.
+ * <p>
+ * 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.
+ * <p>
+ * 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<Native> references = new ReferenceQueue<>();
+
+ private static <T extends Native> T createInstance(Class<T> jtype, MemoryAddress p) {
+ cleanerStep();
+ try {
+ Class[] params = {MemoryAddress.class};
+ Constructor<T> 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 extends Native> T resolve(Class<T> 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 extends Native> T resolve(MemoryAddress p, Function<MemoryAddress, T> 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 <T extends Native> 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<? extends Native> 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 <T extends Native> 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.
+ * <p>
+ * 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<Native> {
+
+ protected MemoryAddress p;
+ final Class<? extends Native> jtype;
+ CHandle next;
+
+ CHandle(Native referent, ReferenceQueue<Native> 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.
+ * <p>
+ * 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.
+ * <p>
+ * This serves two purposes:
+ * <ol>
+ * <li>Track and resolve unique objects based on memory address;
+ * <li>Hold hard references to the WeakReference as required by the gc system.
+ * </ol>
+ * <p>
+ * 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;
+ }
+ }
+}
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 $@ $<
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
#include <string.h>
#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);
}
}
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)
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;
+}
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);
+++ /dev/null
-
-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<Byte> 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<Data> dataLayout = LayoutType.ofStruct(Data.class);
-
- try (Scope s = Libraries.libraryScope(lib).fork()) {
- Pointer<Data> a = s.allocate(dataLayout);
- Pointer<Data> b = s.allocate(dataLayout);
- Callback<Call__I> 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<Byte> 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);
- }
- }
-}
--- /dev/null
+
+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<Call__i32> 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<Call_i32_v> 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);
+ }
+ }
+}
--- /dev/null
+
+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
--- /dev/null
+#!/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
+# <node>... <type> xxx</type> ... <name>xxx</name></node>
+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') {
+ # <type category="funcpointer">typedef void (VKAPI_PTR *<name>PFN_vkInternalAllocationNotification</name>)(
+ # <type>void</type>* pUserData,
+ # <type>size_t</type> size,
+ # <type>VkInternalAllocationType</type> allocationType,
+ # <type>VkSystemAllocationScope</type> allocationScope);</type>
+ 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";
+ }
+ }
+}
--- /dev/null
+#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<M; i++) {
+ z = vec2(z.x*z.x - z.y*z.y, 2.*z.x*z.y) + c;
+
+ if (dot(z, z) > 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;
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+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<value.length;i++)
+ m.setAtIndex(type, i, value[i]);
+ return m;
+ }
+
+ default MemorySegment copy(byte value) {
+ return allocate(Memory.BYTE, value);
+ }
+
+ default MemorySegment copy(short value) {
+ return allocate(Memory.SHORT, value);
+ }
+
+ default MemorySegment copy(int value) {
+ return allocate(Memory.INT, value);
+ }
+
+ default MemorySegment copy(long value) {
+ return allocate(Memory.LONG, value);
+ }
+
+ default MemorySegment copy(float value) {
+ return allocate(Memory.FLOAT, value);
+ }
+
+ default MemorySegment copy(double value) {
+ return allocate(Memory.DOUBLE, value);
+ }
+
+ default MemorySegment copy(byte[] value) {
+ return allocateArray(Memory.BYTE, value);
+ }
+
+ default MemorySegment copy(int[] value) {
+ return allocateArray(Memory.INT, value);
+ }
+
+ default MemorySegment copy(long[] value) {
+ return allocateArray(Memory.LONG, value);
+ }
+
+ default MemorySegment copy(float[] value) {
+ return allocateArray(Memory.FLOAT, value);
+ }
+
+ default public MemorySegment copy(String value) {
+ return allocateUtf8String(value);
+ }
+
+ /*
+ default <T extends Native> 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 <T extends Native> MemorySegment copy(T value) {
+ return copy(value.address());
+ }
+
+ default <T extends Native> 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+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.
+ * <p>
+ * The stack allocator works like this
+ * <pre>
+ * try (Frame f = Memory.createFrame()) {
+ * MemorySegment a = f.allocate(size);
+ * }
+ * </pre>
+ * Any memory allocated is freed when the frame is closed.
+ * <p>
+ * 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<Stack> 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<Byte> 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<Short> 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<Integer> 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<Long> 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<Float> 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<Double> 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<MemoryAddress> 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<T extends Memory.Addressable> extends AbstractList<T> implements Memory.Addressable {
+ final MemorySegment segment;
+ Function<MemoryAddress,T> create;
+
+ public HandleArray(Function<MemoryAddress,T> create, MemorySegment segment) {
+ this.segment = segment;
+ this.create = create;
+ }
+
+ public HandleArray(Frame frame, Function<MemoryAddress,T> 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);
+ }
+ }
+
+}
--- /dev/null
+
+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<T> {
+ NativeSymbol symbol();
+ T function();
+}
+*/
+record FunctionPointer<T>(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<PFN_Test> 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<PFN_Test> 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);
+ }
+ }
+ };
+ }
+}
--- /dev/null
+
+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);
+ }
+}
--- /dev/null
+
+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);
+ }
+}
--- /dev/null
+
+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);
+ }
+}
--- /dev/null
+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<VkPhysicalDevice> getPhysicalDevices() {
+ MemoryLayout.PathElement pe = MemoryLayout.PathElement.groupElement("physicalDevices");
+ MemorySegment seg = segment.asSlice(LAYOUT.byteOffset(pe), LAYOUT.select(pe).byteSize());
+ return new Memory.HandleArray<VkPhysicalDevice>((addr)->new VkPhysicalDevice(addr, instanceDispatch), seg);
+ }
+ Memory.HandleArray<VkPhysicalDevice> 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<VkPhysicalDevice>((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"));
+}
--- /dev/null
+ /*
+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<VkDescriptorSet> descriptorSets = VkDescriptorSet.createArray(scope, 1);
+
+ int computeQueueIndex;
+ VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
+
+ String mandelbrot_entry = "main";
+ Memory.IntArray mandelbrot_cs;
+
+ VkShaderModule mandelbrotShader;
+ VkPipelineLayout pipelineLayout;
+ Memory.HandleArray<VkPipeline> computePipeline = VkPipeline.createArray(scope, 1);
+
+ VkCommandPool commandPool;
+ Memory.HandleArray<VkCommandBuffer> 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<VkPhysicalDevice> devs;
+ int count;
+ int res;
+
+ devs = instance.vkEnumeratePhysicalDevices();
+
+ int best = 0;
+ int devid = -1;
+ int queueid = -1;
+
+ for (int i=0;i<devs.length();i++) {
+ VkPhysicalDevice dev = devs.getAtIndex(i);
+ VkQueueFamilyProperties famprops;
+
+ // TODO: change to return the allocated array directly
+ dev.vkGetPhysicalDeviceQueueFamilyProperties(count$h, null);
+ famprops = VkQueueFamilyProperties.createArray(frame, count$h.getAtIndex(0));
+ dev.vkGetPhysicalDeviceQueueFamilyProperties(count$h, famprops);
+
+ int family_count = count$h.getAtIndex(0);
+
+ for (int j=0;j<family_count;j++) {
+ int score = 0;
+
+ if ((famprops.getQueueFlags(j) & VkQueueFlagBits.VK_QUEUE_COMPUTE_BIT) != 0)
+ score += 1;
+ if ((famprops.getQueueFlags(j) & VkQueueFlagBits.VK_QUEUE_GRAPHICS_BIT) == 0)
+ score += 1;
+
+ if (score > 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<VkDescriptorSetLayout> 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<VkDescriptorSetLayout> 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<VkFence> fences = VkFence.createArray(frame, 1);
+ VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(frame);
+
+ // maybe this should take a HandleArray<Fence> 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();
+ }
+}
--- /dev/null
+
+// 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<VkCommandBuffer> vkAllocateCommandBuffers(VkCommandBufferAllocateInfo pAllocateInfo)throws Exception {
+ int res$code;
+ try {
+ Memory.HandleArray<VkCommandBuffer> 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));
+ }
--- /dev/null
+
+// 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<VkPhysicalDevice> 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<VkPhysicalDevice> 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));
+ }