-dist_VERSION=1.0.99
+dist_VERSION=2.0.99-jdk-foreign
dist_NAME=nativez
dist_EXTRA=README \
build.xml \
Introduction
------------
-This is a C link library and Java base class which can be used to help
-implement libraries which use the Java Native Interface (JNI).
-
-It provides a base class for native objects which provides automatic
-(and manual) reclaimation, and a shared library for easy access to
-these and other JNI utilities.
+This is a set of tools and a Java utility module which can be used to
+implement libraries which use the jdk.foreign mechanisms for calling
+native C libraries.
Compile
-------
`bin/<target>/jmods/notzed.nativez.jmod'
+$ make sdk
+
+Will compile a usable in-tree version to bin/<target>/bin.
+
Other files of import which can be used for an IDE, dependent project
compilation, or non-modular use:
-`bin/<target>/lib/notzed.nativez.jar' - portable modular jar
-`bin/<target>/include' - header files
-`bin/<target>/lib/libnativez.so' - linux shared library
-`bin/<target>/bin/nativez.dll' - microsoft windows dynamic link library
-`bin/<target>/bin/nativez-gen' - table generator
+`bin/<target>/lib/notzed.nativez.jar' - portable modular jar for utility classes.
+`bin/<target>/lib/libexport.so' - gcc plugin for extracting C api information.
+`bin/<target>/bin/export-api' - driver script for api generation.
+`bin/<target>/bin/generate-api' - internal script for C structs/functions.
+`bin/<target>/bin/export-defines' - internal script for #define processing.
`bin/modules/notzed.nativez/' - compiled class files
Usage
-----
-Java Library
-- - - - - --
+The export-api script is used to generate source files from a control
+file. `java.make` includes targets for processing the control file
+from gen.make.
-Define a common base class and derive from
-`au.notzed.nativez.NativeZ'. This (and all derived classes) must
-implement the single long (pointer) constructor and load the
-corresponding dynamic library which implements the native interfaces
-from any derived classes.
+`src/<module>/gen/gen.make` should include targets such as:
-Each derived class should include a `static void release(long p)'
-method which will be called once the object is no longer accessible
-from Java. This will typically be a native method that frees the
-associated resources.
+<module>_API = name
-The module for the library must export the package containing the
-derived classes to the `notzed.nativez' module using the `opens'
-directive.
+This will expect two control files:
-Native Library
-- - - - - - --
+`src/<module>/gen/name.api` - api control file
+`src/<module>/gen/name.h` - header file which includes all C headers used by the control file.
-Whilst not necessary one can use the native library for access to the
-NativeZ_* functions and some other cross-platform tools.
-The native library should link with -lnativez and include "nativez.h".
+The <name>.api file is somewhat complex and still in development,
+there is little documentation apart from examples in other projects
+and the source code in lib/*.pm.
-Before using any functions invoke the nativez_OnLoad() function,
-typically in the native library's JNI_OnLoad() function.
+The basic format however is:
-Any instance of a class derived from NativeZ must be created or
-referenced using the NativeZ_* functions in order to partake of
-automatic cleanup.
+thing name options {
+ entry options...;
+ entry options...;
+}
-Native functions should take object arguments and resolve them to
-native pointers using NativeZ_getP().
+Options are of the form:
-The nativez_* functions are just general JNI and useful cross-platform
-utility functions which may be freely used within (any) JNI code.
+ flag - simple flag
+ class:flag - flag for a specific class of variable
+ option=value - an option
+ class:option=value - option on a specific class.
-See nativez.h for more information.
+No spaces are allowed in options or values, there are no quoting rules
+and tokens are separated by whitespace.
-nativez-gen
--- - - - --
+The whole { } block can be replaced by {{ }} which is a literal, and
+no internal processing will be performed.
-nativez provides a couple of functions for compact table based
-resolution of shared library entry points from dynamically loaded
-libraries, and for resolving Java classes and field and method
-acccessors.
+Each named option may also end with a literal block.
-Whilst the tables can be generated manually nativez-gen can be used to
-generate correct tables from a definition file. When run on a
-definition file it will create one or two pairs of static variables to
-be included in the C file that uses it.
+type
+----
-nativez-gen requires `perl' and the header section processing requires
-`cproto'.
+Types define handling templates for canonicalised C types.
-The definition file is made of any number of sections. Each section
-starts with a header which must be on the same line (including the
-`{') and a number of entries each on their own line followed by a `}'.
-Non-EOL whitespace is ignored. No quoting or escaping is available.
+library
+-------
-See also `src/notzed.nativez/jni/nativez.gen'. It's dumb but somewhat
-messy perl.
+A library type defines a class which will contain static fields and
+members. Each entry specifies which objects to include,
+e.g. func[tions], define[s], enum[erations] and are specified by a
+regex.
-See also `src/notzed.nativez/jni/jni.make' for an example of how to
-invoke it automatically.
+If a library is referenced by a struct it is not generated directly
+but included within that struct.
-java section
-- -- -- --
+Included functions can be renamed, can define in/out parameters,
+return code processing, and whether functions are static or not, or
+constructors.
-Each entry must be on a separate line as below.
+struct
+------
-java VarName com/package/name/ClassName {
- static funcName, funcSig
- funcName, funcSig
- static fieldName,fieldSig
- fieldName,fieldSig
-}
+A struct defines an object which maps directly to a struct of the same
+name. Each entry specifies which structure fields to include as
+public accessors but it can also include constants and functions.
-VarName must be unique for each section in a single definition file.
-
-The *Name* and *Sig values are those passed to the corresponding JNI
-functions. The static modifier calls the *Static* variaants. A
-funcSig will contain a pair of parentheses and a fieldSig will not.
-
-For each java section the following definitions will be created:
-
-static struct {
- jclass VarName_classid;
- jmethodID funcName_[encodedSig];
- jmethodID funcName_[encodedSig];
- jfieldID fieldName_[encodedSig];
- jfieldID fieldName_[encodedSig];
-} java;
-
-static const char *java_names =
- "#com/package/name/ClassName\0"
- ":funcName\0funcSig\0"
- ".funcName\0funcSig\0"
- ";fieldName\0fieldSig\0"
- ",fieldName\0fieldSig\0"
- ;
-
-encodedSig depends on the arguments to nativez-gen. By default for
-functions a compact variation of JNI name mangling is used where the
-primitive types are lowercased and any object types are converted to
-'l' with all [ collapsed to nothing. A longer option (--java-long) is
-somewhat similar but not (current) quite the same as the JNI name
-mangling and allows for overriden functions taking different object
-types in the same position.
-
-See the nativez-gen header for the rest of the options.
-
-header section
- -- -- -- --
-
-A header section extracts prototypes from C .h header files and
-creates a typesafe structure of same.
-
-header libname path/header.h {
- funcname_a
- funcmame_b
-}
+The name `<default>` can be used to apply to all structures defined by
+including `<name>.h`.
-libname is the name of the library as used in the
-nativez_ResolveLibraries() table. i.e. it's just a key and not the
-actual name.
+enum
+----
-path/header.h should be the name as included directly in a C file,
-i.e. it can be used direclty in a #include directive. It is relative
-to the (-b path) command line argument.
+Defines any special handling of enums.
-The contents of the section are just the function names required.
+define
+------
-All header sections in a definition file will be combined to create a
-pair of variables. If one assumes a second section for libname2 is in
-the file, this is an example output:
+Includes a list of patterns to include or exclude from the list of
+defines generated by the header file. It can also be filtered by
+which header defined the value, so for example that system header
+files are ignored.
-#inclulde <path/header.h>
-#inclulde <path/header2.h>
+Only simple defines can be included (those that resolve to a basic C
+type or string). The define exporter uses the C compiler to resolve
+the types and automatically determine their base type.
-static struct {
- /* libname */
- type (*funcname_a)(args);
- type (*funcname_b)(args);
- /* libname2 */
- type (*funcname_c)(args);
-} fn;
+code
+----
-static const char *fn_names =
- "#libname\0"
- "funcname_a\0"
- "funcname_b\0"
- "@libname2\0"
- "funcname_c\0"
- ;
+Defines Java code template fragments. The template system uses a
+simple {name} which resolves to a string which is context-sesitive.
+Templates are expanded recursively.
-See the nativez-gen header for the options.
+The code generator is pretty messy at this point and still work in
+progress.
LICENSE
-------
JAVAC ?= $(JAVA_HOME)/bin/javac
JAR ?= $(JAVA_HOME)/bin/jar
JMOD ?= $(JAVA_HOME)/bin/jmod
+GCCPLUGINDIR:=$(shell gcc -print-file-name=plugin)
JAVACFLAGS +=
-I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux
linux-amd64_CFLAGS = -fPIC -Os -Wall
linux-amd64_CC = cc
+linux-amd64_CXXFLAGS = -fPIC -Os -Wall
+linux-amd64_CXX = g++
linux-amd64_LD = ld
linux-amd64_SO = .so
windows-amd64_CFLAGS = -Os -Wall
windows-amd64_CC = x86_64-w64-mingw32-gcc
windows-amd64_LD = x86_64-w64-mingw32-ld
+windows-amd64_CXXFLAGS = -Os -Wall
+windows-amd64_CXX = x86_64-w64-mingw32-g++
windows-amd64_LDFLAGS = -Wl,--subsystem,windows
windows-amd64_SO = .dll
windows-amd64_LIB =
+
+$(info target $($(TARGET)_CC))
#
-# Copyright (C) 2019 Michael Zucchi
+# Copyright (C) 2019,2022 Michael Zucchi
#
# This is the copyright for java.make
#
# generators must exist in src/<module>/gen. Native
# libraries must exist in src/<module>/jni.
+# native_MODULES list of native-only "modules".
+
+
# Global variables
# JAVA_HOME location of jdk.
# JARFLAGS jar flags
# JMOD jmod command.
# JMODFLAGS jmod flags.
+# JAVAFLAGS java flags for run targets
# Module specific variables
# <module>_JARFLAGS
# <module>_JMODFLAGS
+# all paths are relative to the root package name
+
# <module>_JAVA Java sources. If not set it is found from src/<module>/classes/(*.java)
# <module>_RESOURCES .jar resources. If not set it is found from src/<module>/classes/(not *.java)
-# <module>_JAVA_GENERATED Java generated sources. These must be relative to the package name.
+# <module>_JAVA_GENERATED Java generated sources.
+# <module>_RESOURCES_GENERATED Java generated sources.
# Variables for use in fragments
# <module>_gendir Location for files used in Java generation process (per project).
# <module>_genjavadir Location where _JAVA_GENERATED .java files will be created (per project).
-# <module>_jnidir Location for jni generated files (per target).
# <module>_objdir Location for c objects (per target).
# <module>_incdir Location for output includes, .jmod staging.
# <module>_libdir Location for output libraries, .jmod staging. May point to _bindir.
# These are compiled after the java sources have been compiled as that
# process also generates any native binding headers.
-# <module>_JNI_LIBRARIES list of libraries to build.
+# <module>_NATIVE_LIBRARIES list of libraries to build.
# library names match System.loadLibrary().
# Global variables
# <target>_LDLIBS
# <target>_CPPFLAGS
# <target>_CFLAGS
+# <target>_CC
+# <target>_CXXFLAGS
+# <target>_CXX
# SO shared library suffix
# LIB shared library prefix
# Per library variables.
-# <library>_SOURCES .c, .cc, .C - source files for library. Paths are relative to src/<module>/jni.
-# <library>_HEADERS header files for jmod
-# <library>_COMMANDS commands/bin/scripts for jmod
+# <library>_SOURCES .c source files for library. Paths are relative to src/<module>/native.
+# <library>_CXXSOURCES .c source files for library. Paths are relative to src/<module>/native.
+# <library>_HEADERS header files for install/jmod
+# <library>_COMMANDS commands/bin/scripts for install/jmod
# <library>_LDFLAGS link flags
# <library>_LIBADD extra objects to add to link line
# <library>_LDLIBS link libraries
-# <library>_CPPFLAGS c pre-processor flags. "-Isrc/<module>/jni -Ibin/include/<module>" is implicit.
+# <library>_CPPFLAGS c and c++ pre-processor flags. "-Isrc/<module>/jni -Ibin/include/<module>" is implicit.
# <library>_CCFLAGS c compiler flags
+# <library>_CXXFLAGS c++ compiler flags
# <library>_DEPENDENCIES A list of other objects on which this library depends before linking.
-# <library>_DEFS A list of .def files for nativez-gen.
-# <library>_DEFSFLAGS Flags for nativez-gen invocation.
-
-# .c files have dependencies automatically generated
+# .c and .cc files have dependencies automatically generated
# Targets
# -------
# make clean rm -rf bin
# make dist create dist tar in bin/
# make | make jar make all jars and jmods
-# make bin make everything but jars and mods
# Outputs
# -------
# ######################################################################
+all_MODULES = $(java_MODULES) $(native_MODULES)
+
E:=
S:=$(E) $(E)
-
-# All modules with native code
-java_JMODS=$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/jni/jni.make),$(module)))
-# Only modules with no native code
-java_JARS=$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/jni/jni.make),,$(module)))
-# Modules with generated java source
-java_JGEN=$(foreach module,$(java_MODULES),$(if $(wildcard src/$(module)/gen/gen.make),$(module)))
+SO=$($(TARGET)_SO)
+LIB=$($(TARGET)_LIB)
# Define some useful variables before including fragments
+define common_variables=
+$1_gendir:=bin/gen/$1/gen
+$1_genjavadir:=bin/gen/$1/classes
+$1_objdir:=bin/$1/$(TARGET)/obj
+$1_incdir:=bin/$1/$(TARGET)/include
+$1_libdir:=$$(if $$(filter windows-%,$(TARGET)),bin/$1/$(TARGET)/bin,bin/$1/$(TARGET)/lib)
+$1_bindir:=bin/$1/$(TARGET)/bin
+endef
+
define java_variables=
-$(1)_gendir:=bin/gen/$(1)/gen
-$(1)_genjavadir:=bin/gen/$(1)/classes
-$(1)_jnidir:=bin/$(1)/$(TARGET)/jni
-$(1)_objdir:=bin/$(1)/$(TARGET)/obj
-$(1)_incdir:=bin/$(1)/$(TARGET)/include
-$(1)_libdir:=$$(if $$(filter windows-%,$(TARGET)),bin/$(1)/$(TARGET)/bin,bin/$(1)/$(TARGET)/lib)
-$(1)_bindir:=bin/$(1)/$(TARGET)/bin
-ifndef $(1)_JAVA
-$(1)_JAVA := $$(shell find src/$(1)/classes -type f -name '*.java')
+ifndef $1_JAVA
+$1_JAVA := $$(shell cd src/$1/classes && find * -type f -name '*.java')
endif
-ifndef $(1)_RESOURCES
-$(1)_RESOURCES := $$(shell find src/$(1)/classes -type f \! -name '*.java')
+ifndef $1_RESOURCES
+$1_RESOURCES := $$(shell cd src/$1/classes && find * -type f \! -name '*.java')
endif
endef
java_incdir:=bin/$(TARGET)/include
java_jmoddir:=bin/$(TARGET)/jmods
+$(foreach module,$(java_MODULES) $(native_MODULES),$(eval $(call common_variables,$(module))))
$(foreach module,$(java_MODULES),$(eval $(call java_variables,$(module))))
# ######################################################################
-all: jar
-bin:
+all:
+jar:
gen:
-.PHONY: all clean jar bin gen
+.PHONY: all clean jar gen $(java_MODULES)
clean:
rm -rf bin
-include $(patsubst %,src/%/gen/gen.make,$(java_JGEN))
-include $(patsubst %,src/%/jni/jni.make,$(java_JMODS))
+# Gen is things that go into the jar (sources and resources)
+include $(wildcard $(all_MODULES:%=src/%/gen/gen.make))
+# Native is things that go into the sdk/jmod
+include $(wildcard $(all_MODULES:%=src/%/native/native.make))
+
+# ######################################################################
+
+# create module depencies
+# variables:
+# <module>_sdk is the target location of an expanded 'sdk' for this module
+# it resides in a common location bin/<target>/
+# <module>_jmod is the target location of a staging area for jmod files
+# is resides in a per-module lcoation bin/<module>/<target>/
+# <module>_java is all the targets that will cause the invocation of javac
+# it includes the module source, generated sources, and sentinals for generated sources
+
+# targets:
+# bin/status/<module>.depjava marks all source/generated sources are ready/updated
+# bin/status/<module>.depjar all compiled class files and resources are ready/updated
+# bin/status/<module>.sdk all files are available in bin/<target> as if it was an installed image
+
+define module_vars=
+$1_sdk := $(addprefix $(java_bindir)/,$($1_COMMANDS)) $(addprefix $(java_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$(java_libdir)/lib%.so)
+$1_jmod := $(addprefix $($1_bindir)/,$($1_COMMANDS)) $(addprefix $($1_libdir)/,$($1_LIBRARIES)) $($1_NATIVE_LIBRARIES:%=$($1_libdir)/lib%.so)
+$1_java :=$($1_JAVA:%=src/$1/classes/%) $($1_JAVA_GENERATED:%=$($1_genjavadir)/%)
+$1_resources:= $($1_RESOURCES:%=src/$1/classes/%) $($1_RESOURCES_GENERATED:%=$($1_genjavadir)/%)
+$1_depjava := $($1_API:%=bin/status/$1-%.export) $(patsubst %,bin/status/%.classes, $(filter $($1_JDEPMOD),$(java_MODULES)))
+
+ifneq ("$$(strip $$($1_java) $$($1_depjava))", "")
+bin/status/$1.depjava: $$($1_java) $$($1_depjava)
+ @install -d $$(@D)
+ touch $$@
+bin/status/$1.depjar: bin/status/$1.classes $$($1_resources)
+ @install -d $$(@D)
+ touch $$@
+bin/status/$1.depmod: bin/status/$1.classes $$($1_resources) $$($1_jmod)
+ @install -d $$(@D)
+ touch $$@
+bin/status/$1.sdk: $(java_jardir)/$1.jar
+jar: $(java_jardir)/$1.jar
+gen: bin/status/$1.depjava
+$1 all: $(java_jardir)/$1.jar $(java_jmoddir)/$1.jmod
+else
+# acutally not sure here?
+$1 all: bin/status/$1.sdk
+endif
+
+$1-sdk sdk: bin/status/$1.sdk
+
+bin/status/$1.sdk: $$($1_sdk) $$($1_jmod)
+ @install -d $$(@D)
+ touch $$@
+
+endef
+
+#$(foreach m,$(all_MODULES),$(info $(call module_vars,$m)))
+$(foreach m,$(all_MODULES),$(eval $(call module_vars,$m)))
+
+# ######################################################################
+# notzed.nativez export-api
+# ######################################################################
+
+define api_targets=
+bin/status/$1-$2.export: src/$1/gen/$2.api src/$1/gen/$2.h
+bin/status/$1-$2.export:
+ mkdir -p bin/gen/$1/gen bin/status
+ $(NATIVEZ_HOME)/bin/export-api \
+ -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api
+ touch $$@
+
+bin/status/$1-$2.export.d:
+ @$(NATIVEZ_HOME)/bin/export-api -M -MT "$$(@:.d=) $$@" -MF $$@ \
+ -w bin/gen/$1/gen -d bin/gen/$1/classes $($1_APIFLAGS) $($1_$2_APIFLAGS) src/$1/gen/$2.api 2>/dev/null
+
+$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include bin/status/$1-$2.export.d)
+endef
+
+$(foreach m,$(all_MODULES),$(foreach a,$($m_API),$(eval $(call api_targets,$m,$a))))
# ######################################################################
# Java
# ######################################################################
+# Build targets for java modules
+
define java_targets=
-# Rules for module $(1)
-$(1)_JAVA_generated = $$(addprefix $$($(1)_genjavadir)/,$$($(1)_JAVA_GENERATED))
-
-bin/status/$(1).data: $$($(1)_RESOURCES)
-bin/status/$(1).classes: $(patsubst %,bin/status/%.classes,$($(1)_JDEPMOD)) $$($(1)_JAVA) $$($(1)_JAVA_generated)
-jar $(1): $(java_jardir)/$(1).jar $(java_jmoddir)/$(1).jmod
-bin: bin/status/$(1).classes bin/status/$(1).data
-sources: $(java_jardir)/$(1)-sources.zip
-gen: $$($(1)_JAVA_generated)
-
-# Create modular jar
-$(java_jardir)/$(1).jar: bin/status/$(1).classes bin/status/$(1).data
+
+# Create (modular) jar
+$(java_jardir)/$1.jar: bin/status/$1.depjar
@install -d $$(@D)
$(JAR) cf $$@ \
$(JARFLAGS) $$($(1)_JARFLAGS) \
- -C bin/modules/$(1) .
+ -C bin/modules/$(1) . \
+ $(if $($1_RESOURCES),$($1_RESOURCES:%=-C src/$1/classes %)) \
+ $(if $($1_RESOURCES_GENERATED),$($1_RESOURCES_GENERATED:%=-C bin/gen/$1/classes %))
# Create a jmod
-$(java_jmoddir)/$(1).jmod: bin/status/$(1).classes bin/status/$(1).data
+$(java_jmoddir)/$1.jmod: bin/status/$1.depmod
rm -f $$@
@install -d $$(@D)
$$(JMOD) create \
$$@
# Create an IDE source zip, paths have to match --module-source-path
-$(java_jardir)/$(1)-sources.zip: bin/status/$(1).classes
+$(java_jardir)/$1-sources.zip: bin/status/$1.depjar
@install -d $$(@D)
- jar -c -f $$@ -M \
- $$(patsubst src/$(1)/classes/%,-C src/$(1)/classes %,$$(filter src/$(1)/classes/%,$$($(1)_JAVA))) \
- $$(patsubst bin/gen/$(1)/classes/%,-C bin/gen/$(1)/classes %,$$(filter bin/gen/$(1)/classes/%,$$($(1)_JAVA)))
-
-endef
+ $(JAR) -c -f $$@ -M \
+ $$($1_JAVA:%=-C src/$1/classes %) \
+ $$($1_JAVA_GENERATED:%=-C bin/gen/$1/classes %)
-#$(foreach module,$(java_MODULES),$(info $(call java_targets,$(module))))
-$(foreach module,$(java_MODULES),$(eval $(call java_targets,$(module))))
+# resources
+bin/modules/$1/%: src/$1/classes/%
+ install -vD $$< $$@
-# ######################################################################
-# Global pattern rules
-
-# Stage resources
-bin/status/%.data:
- @install -d $(@D)
- for data in $(patsubst src/$*/classes/%,"%",$($*_RESOURCES)) ; do \
- install -vDC "src/$*/classes/$$data" "bin/modules/$*/$$data" || exit 1 ; \
- done
- touch $@
-
-# Compile one module. This only updates (javac -h) headers if they changed.
-bin/status/%.classes:
- @install -d $(@D)
+# Compile module.
+bin/status/$1.classes: bin/status/$1.depjava
+ @install -d $$(@D)
$(JAVAC) \
--module-source-path "src/*/classes:bin/gen/*/classes" \
$(if $(JAVAMODPATH),--module-path $(subst $(S),:,$(JAVAMODPATH))) \
- $(JAVACFLAGS) $($*_JAVACFLAGS) \
- -h bin/inc \
+ $(JAVACFLAGS) $($1_JAVACFLAGS) \
-d bin/modules \
- -m $* \
- $($*_JAVA) $($*_JAVA_generated)
- if [ -d bin/inc/$* ] ; then \
- install -DC -t bin/include/$* bin/inc/$*/*.h ; \
- fi
- touch $@
+ -m $1 \
+ $$($1_JAVA:%=src/$1/classes/%) \
+ $$($1_JAVA_GENERATED:%=bin/gen/$1/classes/%)
+ touch $$@
+endef
+
+#$(foreach module,$(java_MODULES),$(info $(call java_targets,$(module))))
+$(foreach module,$(java_MODULES),$(eval $(call java_targets,$(module))))
-# ######################################################################
-# C stuff
# ######################################################################
-SUFFIXES=.c .C .cc
-SO=$($(TARGET)_SO)
-LIB=$($(TARGET)_LIB)
+# setup run-* targets
+define run_targets=
+run-$1/$2: bin/status/$1.sdk $($1_JDEPMOD:%=bin/status/%.sdk)
+ LD_LIBRARY_PATH=$(FFMPEG_HOME)/lib \
+ $(JAVA) \
+ $(if $(strip $(JAVAMODPATH) $($1_JAVAMODPATH)),--module-path $(subst $(S),:,$(strip $(JAVAMODPATH) $($1_JAVAMODPATH)))) \
+ $(JMAINFLAGS) $($1_JMAINFLAGS) \
+ -m $1/$2 \
+ $(ARGV)
+.PHONY: run-$1/$2
+endef
-# functions to find cross-module stuff $(call library-path,modname,libname)
-library-path=$($(1)_libdir)/$(LIB)$(2)$(SO)
-library-dir=$($(1)_libdir)/
+#$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(info $(call run_targets,$(module),$(main)))))
+$(foreach module,$(java_MODULES),$(foreach main,$($(module)_JMAIN),$(eval $(call run_targets,$(module),$(main)))))
-define jni_library=
-# Rule for library $(2) in module $(1)
-$(2)_OBJS = $(foreach sx,$(SUFFIXES),$(patsubst %$(sx), $($(1)_objdir)/%.o, $(filter %$(sx),$($(2)_SOURCES))))
-$(2)_SRCS = $(addprefix src/$(1)/jni/,$($(2)_SOURCES))
-$(2)_SO = $($(1)_libdir)/$(LIB)$(2)$(SO)
+# ######################################################################
+# C and c++ native library support
+# ######################################################################
-$($(1)_libdir)/$(LIB)$(2)$(SO): $$($(2)_OBJS) $($(2)_LIBADD) $($(2)_DEPENDENCIES)
- @install -d $$(@D)
- $($(TARGET)_CC) -o $$@ -shared \
- $($(TARGET)_LDFLAGS) $($(2)_LDFLAGS) $$($(2)_OBJS) $($(2)_LIBADD) $($(TARGET)_LDLIBS) $($(2)_LDLIBS)
+define native_library=
+# Rule for library $$2 in module $$1
+$2_OBJS = $(patsubst %.c, $($1_objdir)/%.o, $($2_SOURCES)) \
+ $(patsubst %.cc, $($1_objdir)/%.o, $($2_CXXSOURCES))
+$2_SRCS = $(addprefix src/$1/native/,$($2_SOURCES))
+$2_SO = $($1_libdir)/$(LIB)$2$(SO)
+# Copy anything from staging area for jmods bin/module/<target>/* to sdk area bin/<target>/*
$(java_libdir)/%: $($(1)_libdir)/%
- install -DC $$< $$@
+ @install -d $$(@D)
+ ln -fs $$(abspath $$<) $$@
$(java_bindir)/%: $($(1)_bindir)/%
- install -DC $$< $$@
+ @install -d $$(@D)
+ ln -fs $$(abspath $$<) $$@
$(java_incdir)/%: $($(1)_incdir)/%
- install -DC $$< $$@
+ @install -d $$(@D)
+ ln -fs $$(abspath $$<) $$@
-$($(1)_objdir)/%.o: src/$(1)/jni/%.c
+$($1_libdir)/$(LIB)$2$(SO): $$($2_OBJS) $($2_LIBADD) $($2_DEPENDENCIES)
@install -d $$(@D)
- $($(TARGET)_CC) -Isrc/$(1)/jni -Ibin/include/$(1) -I$($(1)_jnidir) \
- $($(TARGET)_CPPFLAGS) $($(2)_CPPFLAGS) \
- $($(TARGET)_CFLAGS) $($(2)_CFLAGS) -c -o $$@ $$<
+ $($(TARGET)_CC) -o $$@ -shared \
+ $($(TARGET)_LDFLAGS) $($2_LDFLAGS) $$($2_OBJS) $($2_LIBADD) $($(TARGET)_LDLIBS) $($2_LDLIBS)
-$($(1)_incdir)/%.h: src/$(1)/jni/%.h
- install -DC $$< $$@
+$($1_objdir)/%.o: src/$1/native/%.c
+ @install -d $$(@D)
+ $($(TARGET)_CC) -Isrc/$1/native -Ibin/include/$1 \
+ $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \
+ $($(TARGET)_CFLAGS) $($2_CFLAGS) -c -o $$@ $$<
+
+$($1_objdir)/%.o: src/$1/native/%.cc
+ @install -d $$(@D)
+ $($(TARGET)_CXX) -Isrc/$1/native -Ibin/include/$1 \
+ $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) \
+ $($(TARGET)_CXXFLAGS) $($2_CXXFLAGS) -c -o $$@ $$<
# auto-dependencies for c files
-$($(1)_objdir)/%.d: src/$(1)/jni/%.c bin/status/$(1).classes
+$($1_objdir)/%.d: src/$1/native/%.c
@install -d $$(@D)
@rm -f $$@
- @$($(TARGET)_CC) -MM -MT "bin/$(1)/$(TARGET)/obj/$$*.o" -Isrc/$(1)/jni -Ibin/include/$(1) \
- $($(TARGET)_CPPFLAGS) $($(2)_CPPFLAGS) $$< -o $$@.d 2>/dev/null
- @sed 's,\($$*\.o\) *:,\1 $$@ : ,g' $$@.d > $$@ ; rm $$@.d
+ @$($(TARGET)_CC) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \
+ $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null
-# .def files for nativez mapping
-$($(1)_jnidir)/%.h: src/$(1)/jni/%.def
+# auto-dependencies for c++ files
+$($1_objdir)/%.d: src/$1/native/%.cc
@install -d $$(@D)
- $(NATIVEZ_HOME)/bin/nativez-gen -J $($(2)_DEFSFLAGS) $$< > $$@ || ( rm $$@ ; exit 1)
-
-bin jni $(1) $(java_jmoddir)/$(1).jmod: \
- $($(1)_libdir)/$(LIB)$(2)$(SO) \
- $(java_libdir)/$(LIB)$(2)$(SO) \
- $(addprefix $($(1)_incdir)/,$($(2)_HEADERS)) \
- $(addprefix $(java_incdir)/,$($(2)_HEADERS)) \
- $(addprefix $($(1)_bindir)/,$($(2)_COMMANDS)) \
- $(addprefix $(java_bindir)/,$($(2)_COMMANDS)) \
- $(addprefix $($(1)_libdir)/,$($(2)_LIBRARIES))
-
-$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include $$($(2)_OBJS:.o=.d))
-endef
+ @rm -f $$@
+ @$($(TARGET)_CXX) -MM -MT "$$(@:.d=.o) $$@" -Isrc/$1/jni -Ibin/include/$1 \
+ $($(TARGET)_CPPFLAGS) $($2_CPPFLAGS) $$< -o $$@ 2>/dev/null
-#$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(info $(call jni_library,$(module),$(library)))))
-$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(eval $(call jni_library,$(module),$(library)))))
+$(if $(filter clean dist gen,$(MAKECMDGOALS)),,-include $$($2_OBJS:.o=.d))
+endef
-#$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(foreach def,$($(library)_DEFS),$(info $($(module)_objdir)/$(def:.def=.o): $($(module)_jnidir)/$(def:.def=.h)))))
-$(foreach module,$(java_JMODS),$(foreach library,$($(module)_JNI_LIBRARIES),$(foreach def,$($(library)_DEFS),$(eval $($(module)_objdir)/$(def:.def=.o): $($(module)_jnidir)/$(def:.def=.h)))))
+#$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(info $(call native_library,$(module),$(library)))))
+$(foreach module,$(all_MODULES),$(foreach library,$($(module)_NATIVE_LIBRARIES),$(eval $(call native_library,$(module),$(library)))))
# ######################################################################
@install -d bin
tar cfz bin/$(dist_NAME)-$(dist_VERSION).tar.gz \
--transform=s,^,$(dist_NAME)-$(dist_VERSION)/, \
- config.make java.make Makefile src \
+ config.make.in java.make Makefile src \
$(dist_EXTRA)
-
javac.processormodulepath=
javac.processorpath=\
${javac.classpath}
-javac.source=13
-javac.target=13
+javac.source=18
+javac.target=18
javac.test.classpath=\
${javac.classpath}
javac.test.modulepath=\
--- /dev/null
+#!/usr/bin/perl
+
+# meta script to run everything at once.
+
+# TODO: add an option that dumps out the make dependencies
+
+use strict;
+use File::Basename;
+use File::Spec::Functions qw(abs2rel);
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+
+use Data::Dumper;
+use File::Path qw(make_path);
+use File::Basename;
+
+use config;
+
+my $apidef;
+my $apibase;
+my $apih;
+my $mode = 'generate';
+my $var = {
+ package => 'api',
+ output => 'bin',
+ workdir => 'bin',
+ verbose => 0,
+ include => [],
+};
+
+while (@ARGV) {
+ my $cmd = shift(@ARGV);
+
+ if ($cmd eq '-MT') {
+ $var->{'make-target'} = shift;
+ } elsif ($cmd eq '-MF') {
+ $var->{'make-file'} = shift;
+ } else {
+ if ($cmd =~ m/^(-[^-])(.+)/) {
+ $cmd = $1;
+ unshift @ARGV, $2;
+ }
+
+ if ($cmd eq "-t") {
+ $var->{package} = shift;
+ } elsif ($cmd eq "-d") {
+ $var->{output} = shift;
+ } elsif ($cmd eq "-w") {
+ $var->{workdir} = shift;
+ } elsif ($cmd eq "-I") {
+ push @{$var->{include}}, shift;
+ } elsif ($cmd eq "-v") {
+ $var->{verbose}++;
+ } elsif ($cmd eq '-M') {
+ $mode = 'make-rule';
+ } else {
+ $apidef = $cmd;
+ $apih = "$1.h" if ($apidef =~ m/^(.*).api$/);
+ $apibase = basename($apidef, '.api');
+ }
+ }
+}
+
+push @{$var->{include}}, "$FindBin::Bin/../lib";
+
+#print Dumper($var);
+
+die ("Missing config argument") if !defined($apidef);
+die ("Unable to find config: $apidef") if !-f $apidef;
+die ("Unable to find matching header for: $apidef") if !-f $apih;
+
+my $api = new config($var, $apidef);
+my @includes = map { ('-I', $_ ) } @{$var->{include}};
+
+if ($mode eq 'make-rule') {
+ my @list = ();
+
+ $var->{'make-target'} = "$apibase.d" if !defined $var->{'make-target'};
+
+ my @args = (
+ '-M',
+ '-MT', $var->{'make-target'},
+ @includes,
+ $apih);
+
+ open (my $gcc, '-|', 'gcc', @args) // die("command failed $!");
+ while (<$gcc>) {
+ chop;
+ s/ *\\$//;
+ s/^ +//;
+ push @list, $_;
+ }
+ close($gcc);
+
+ foreach my $m (qw(export-api export-defines generate-api)) {
+ push @list, abs2rel("$FindBin::Bin/$m");
+ }
+ foreach my $m (qw(api.pm code.pm config.pm method.pm tokenise.pm)) {
+ push @list, abs2rel("$FindBin::Bin/../lib/$m");
+ }
+
+ push @list, $apidef;
+ push @list, map { abs2rel($_) } @{$api->{includes}};
+ push @list, map { dirname($apidef)."/$_->[1]" } grep { $_->[0] eq '%require' } @{$api->{pragmas}};
+
+ if (defined $var->{'make-file'}) {
+ open (my $f, ">", $var->{'make-file'}.'~') || die "writing $var->{'make-file'}";
+ print $f join(" \\\n ", @list)."\n";
+ close ($f) || die "writing $var->{'make-file'}";
+ rename($var->{'make-file'}.'~', $var->{'make-file'}) || die "writing $var->{'make-file'}";
+ } else {
+ print join(" \\\n ", @list)."\n";
+ }
+ exit 0;
+}
+
+my @cmd = (
+ 'gcc',
+ "-fplugin=$FindBin::Bin/../lib/libexport.so",
+ "-fplugin-arg-libexport-output=$var->{workdir}/$apibase.pm",
+# "-fplugin-arg-libexport-verbose=$var->{verbose}",
+ '-O0',
+ '-o', '/dev/null',
+ @includes,
+ $apih
+);
+
+print join " ", @cmd, "\n";
+system(@cmd) == 0 || die("command failed");
+
+my @defines = ();
+if (grep { $_->{type} eq 'define' } @{$api->{objects}}) {
+ @cmd = (
+ "$FindBin::Bin/export-defines",
+ '--hack-new-format-2',
+ $var->{verbose} ? '-v' : (),
+ '-o',
+ "$var->{workdir}/$apibase-defines.pm",
+ @includes,
+ $apidef
+ );
+
+ print join " ", @cmd, "\n";
+ system(@cmd) == 0 || die("command failed");
+ push @defines, '-a', "./$var->{workdir}/$apibase-defines.pm";
+}
+
+@cmd = (
+ "$FindBin::Bin/generate-api",
+ $var->{verbose} ? '-v' : (),
+ '-t', $var->{package},
+ '-d', $var->{output},
+ '-a', "./$var->{workdir}/$apibase.pm",
+ @defines,
+ $apidef
+);
+print join " ", @cmd, "\n";
+system(@cmd) == 0 || die("command failed");
--- /dev/null
+#!/usr/bin/perl
+
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+
+use File::Basename;
+use Data::Dumper;
+
+#require genconfig;
+require config;
+
+my @includes = ();
+my $header;
+my $control = "api-defines.def";
+my $output;
+my $hackformat;
+
+while (@ARGV) {
+ my $cmd = shift;
+
+ if ($cmd =~ m/^(-[^-])(.+)/) {
+ $cmd = $1;
+ unshift @ARGV, $2;
+ }
+
+ if ($cmd eq "-t") {
+ $package = shift;
+ } elsif ($cmd eq "-o") {
+ $output = shift;
+ } elsif ($cmd eq "-v") {
+ $verbose++;
+ } elsif ($cmd eq "-I") {
+ push @includes, shift;
+ } elsif ($cmd eq "--hack-new-format") {
+ $hackformat = 1;
+ } elsif ($cmd eq "--hack-new-format-2") {
+ $hackformat = 2;
+ } else {
+ $control = $cmd;
+ }
+}
+
+die ("no output specified") if !$output;
+
+my $defs;
+my @xports;
+if ($hackformat == 1) {
+ #$defs = genconfig::loadControlFile($control);
+ #@exports = grep { $_->{type} eq 'define' } @{$defs};
+} elsif ($hackformat == 2) {
+ #push @includes, "$FindBin::Bin/../lib";
+ my $conf = new config({ include => \@includes }, $control);
+ $defs = $conf->{objects};
+ @exports = grep { $_->{type} eq 'define' } @{$defs};
+ foreach $export (@exports) {
+ $export->{import} = $conf->findInclude($export->{options}->[0]);
+ }
+} else {
+ $defs = loadControlFile($control);
+ @exports = @{$defs->{define}};
+}
+my %rawDefines = (); # indexed by header
+
+my $CPPFLAGS = "";
+
+foreach $i (@includes) {
+ $CPPFLAGS .= " '-I$i'";
+}
+
+# flatten the generic format to a more usable format, work out defaults (. and syntax check?)
+foreach $export (@exports) {
+ my $includes = 0, $excludes = 0;
+
+ foreach $inc (@{$export->{items}}) {
+ my @options = @{$inc->{options}};
+
+ next if ($inc->{match} =~ m/^(define|enum):/on);
+
+ $inc->{mode} = "include";
+ foreach $o (@{$inc->{options}}) {
+ if ($o =~ m/^(exclude|include|file-include|file-exclude)$/) {
+ $inc->{mode} = $o;
+ }
+ }
+
+ if ($inc->{match} =~ m@^/(.*)/$@) {
+ print "$export->{name} - $inc->{match} - regex $1\n";
+ $inc->{regex} = qr/$1/;
+ } elsif ($inc->{mode} =~ m/^file-/) {
+ $inc->{regex} = qr@/$inc->{match}$|$inc->{match}$@;
+ } else {
+ $inc->{regex} = qr/^$inc->{match}$/;
+ }
+
+ if ($inc->{mode} =~ m/include/) {
+ $includes += 1;
+ } else {
+ $excludes += 1;
+ }
+ }
+
+ $export->{default} = 'all';
+ $export->{default} = 'none' if ($includes > 0);
+
+ my @options = @{$export->{options}};
+
+ $export->{header} = $options[0];
+
+ foreach $o (@options[1..$#options]) {
+ if ($o =~ m/default=(.*)/) {
+ $export->{default} = $1;
+ } else {
+ print STDERR "unknown defines option '$o'\n";
+ }
+ }
+
+ # insert ignore <built-in> thing
+ if ($export->{default} eq 'all') {
+ unshift @{$export->{items}}, { regex => qr/<built-in>/, mode => 'file-exclude' };
+ }
+
+ die ("no header for '$export->{name}'") if !$export->{header};
+}
+
+# load all defines once and link in
+# keep_comments is a bit broken
+foreach $export (@exports) {
+ my $header = $export->{import};
+
+ if (!defined($rawDefines{$header})) {
+ $rawDefines{$header} = scanDefines($header, { CPPFLAGS=>$CPPFLAGS, keep_comments=>0 });
+ }
+
+ $export->{rawDefines} = $rawDefines{$header};
+}
+
+foreach $export (@exports) {
+ # extract matching #defines
+ my @defines = ();
+
+ foreach $d (@{$export->{rawDefines}}) {
+ my $output = 0;
+ my $matches = 0;
+
+ print "? $d->{name} $d->{file} " if $verbose;
+ foreach $inc (@{$export->{items}}) {
+ if ($inc->{mode} eq "include") {
+ $matches = $d->{name} =~ m/$inc->{regex}/;
+ $output = 1 if $matches;
+ } elsif ($inc->{mode} eq "exclude") {
+ $matches = $d->{name} =~ m/$inc->{regex}/;
+ } elsif ($inc->{mode} eq "file-include") {
+ $matches = $d->{file} =~ m/$inc->{regex}/;
+ $output = 1 if $matches;
+ } elsif ($inc->{mode} eq "file-exclude") {
+ $matches = $d->{file} =~ m/$inc->{regex}/;
+ }
+ print " ($inc->{mode} '$inc->{match}' '$inc->{regex}' =$matches)" if $verbose;
+ last if $matches;
+ }
+
+ $output = 1 if (!$matches && $export->{default} eq "all");
+
+ print " output=$output\n" if $verbose;
+
+ push (@{$export->{export}}, $d) if $output;
+ }
+
+}
+
+# output the .c file
+open (my $fh, ">", "$output-gen.c~") || die("can't open $output-gen,c~ for writing");
+export($fh, \@exports);
+close ($fh) || die("error writing");
+rename "$output-gen.c~","$output-gen.c" || die("error overwriting $output-gen.c");
+
+# compile it
+print "cc -o $output-gen $CPPFLAGS $output-gen.c\n";
+system("cc -o $output-gen $CPPFLAGS $output-gen.c") == 0 || die("error compiling $output-gen.c");
+
+# execute it
+print "$output-gen $output~\n";
+system("$output-gen $output~") == 0 || die("error executing $output-gen");
+print "mv $output~\ $output\n";
+rename "$output~","$output" || die("error overwriting $output");
+
+exit 0;
+
+# ######################################################################
+
+sub uniq {
+ my %seen;
+ return grep { !$seen{$_}++ } @_;
+}
+
+sub export {
+ my $fp = shift;
+ my @exports = @{shift @_};
+
+ print $fp <<END;
+#include <stdio.h>
+#include <stdint.h>
+END
+ foreach $h (uniq map { $_->{header} } @exports) {
+ print $fp "#include \"$h\"\n";
+ }
+
+ # this is effectively a function overloading system so that the
+ # compiler can select the datatype of the evaluation of the
+ # definition.
+ # it should probably be c++
+ print $fp <<END;
+/* note unsigned types are output as signed for java compatability */
+/* unsigned long long might not be 64 bit i guess, could use sizeof i suppose */
+#define FMT(x) \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), float), "value=>'%a', type=>'f32'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), double), "value=>'%a', type=>'f64'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int32_t), "value=>'%d', type=>'i32'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), uint32_t), "value=>'0x%08x', type=>'u32'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), int64_t), "value=>'%ld', type=>'i64'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), uint64_t), "value=>'0x%016lx', type=>'u64'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), unsigned long long), "value=>'0x%016llx', type=>'u64'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long long), "value=>'0x%016llx', type=>'i64'", \\
+ __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), typeof(char[])), "value=>'%s', type=>'string'", "type => undefined" )))))))))
+
+int main(int argc, char **argv) {
+ FILE *fp = fopen(argv[1], "w");
+
+ fputs("{\\n", fp);
+END
+
+ foreach $export (@exports) {
+ print $fp <<END;
+fputs("'define:$export->{name}' => { name => '$export->{name}', type => 'define', values => [\\n", fp);
+END
+ foreach $d (@{$export->{export}}) {
+ my $docomment;
+
+ if ($d->{comment}) {
+ my $comment = $d->{comment};
+ $comment =~ s@(['"])@\\\\\\$1@g;
+ $comment =~ s@\n@\\n@g;
+ $docomment = ", comment => '$comment' ";
+ }
+
+ print $fp <<END;
+ fputs(" { name => \\"$d->{name}\\", ", fp);
+ fprintf(fp, FMT($d->{name}), ($d->{name}));
+ fputs("$docomment},\\n", fp);
+END
+ }
+ print $fp <<END;
+ fputs(" ],\\n},\\n", fp);
+END
+ }
+ print $fp <<END;
+ fprintf(fp, "}\\n");
+ fclose(fp);
+}
+END
+}
+
+# args: filename, \@defines, \@defineList
+# export a c file generator
+sub export_generator {
+ my $filename = shift;
+ my @defines = @{shift @_};
+ my @defineList = @{shift @_};
+}
+
+# args: header, \%options
+# options = {
+# CPPFLAGS => 'flags'
+# keep_comments => 1 to keep comments
+# }
+# returns a list of
+# {
+# name=>'name',
+# comment='comment',
+# file='filename',
+# line=linenumber
+# }
+sub scanDefines {
+ my $header = shift;
+ my $o = shift;
+ my $lastc = "";
+ my $source;
+ my $sourceLine;
+
+ print "Scanning $header\n";
+
+ print "cpp -dD ".($o->{keep_comments} ? '-CC' : '')." $o->{CPPFLAGS} $header\n";
+
+ open (my $in,"-|","cpp -dD ".($o->{keep_comments} ? '-CC' : '')." $o->{CPPFLAGS} $header") // die("Can't find include file: $header");
+ while (<$in>) {
+ # line markers
+ if (m/^\# (\d+) \"([^\"]*)/) {
+ $sourceLine = $1;
+ $source = $2;
+ next;
+ }
+ # accumulate comments
+ # single line comments override multi-line
+ if ($o->{keep_comments}) {
+ if (m@/\*(.*)\*/@) {
+ do {
+ $lastc = $1 if $lastc eq "";
+ s@/\*.*\*/@@;
+ } while (m@/\*(.*)\*/@);
+ } elsif (m@/\*(.*)@) {
+ my $com = "$1\n" if $1;
+ while (<$in>) {
+ chop;
+ if (m@(.*)\*/@) {
+ $com .= $1 if $1;
+ last;
+ } else {
+ $com .= "$_\n";
+ }
+ }
+ $lastc = $com if $com && $lastc eq "";
+ } elsif (m@//(.*)@) {
+ $lastc = $1 if $1;
+ s@//.*@@;
+ }
+ }
+
+ if (m/^\s*#define\s+(\w*)\(/) {
+ # ignore macros
+ $lastc = "";
+ } elsif (m/^\s*#define\s+(\w*)\s*$/) {
+ # ignore empty defines
+ $lastc = "";
+ } elsif (m/^\s*#define\s+(\w+)/) {
+ my $name = $1;
+ my %define = ();
+
+ $define{name} = $name;
+ $define{comment} = $lastc if $lastc ne "";
+ $define{file} = $source;
+ $define{line} = $sourceLine;
+
+ push @defines, \%define;
+
+ $lastc = "";
+ }
+
+ $sourceLine++;
+ }
+ close $in;
+
+ return \@defines;
+}
+
+# TODO: library
+sub loadControlFile {
+ my $path = shift @_;
+ my %def = ();
+ my $target;
+
+ open (my $d,"<",$path);
+
+ while (<$d>) {
+ next if /\s*\#/;
+
+ chop;
+
+ if ($target) {
+ if (m/\s*\}\s*$/) {
+ undef $target;
+ } elsif (/^\s*(\S+)\s*(.*)/) {
+ my @options = split(/\s+/,$2);
+ push @{$target->{items}}, {
+ match => $1,
+ options => \@options
+ };
+ }
+ } elsif (/^(\w+)\s+(\S*)\s*(.*)\s+\{/) {
+ my @options = split(/\s+/,$3);
+
+ $target = {
+ type => $1,
+ name => $2,
+ options => \@options,
+ items => []
+ };
+ push @{$def{$1}}, $target;
+ } elsif (/\S/) {
+ die("invalid line: '$_'");
+ }
+ }
+
+ close $d;
+
+ return \%def;
+}
--- /dev/null
+#!/usr/bin/perl
+
+# TODO: formatLibrary - code:<inline> has no template applied, no vars available to do so
+
+# usage
+# -t package target package
+# -d directory output root
+# -v verbose
+# -a datafile add datafile to the dataset, can be from export.so or export-defines, etc
+
+use Data::Dumper;
+
+use File::Path qw(make_path);
+use File::Basename;
+
+use strict;
+
+use Carp 'verbose';
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+
+use api;
+use code;
+use method;
+
+$SIG{__DIE__} = sub { Carp::confess( @_ ) };
+$SIG{'INT'} = sub { Carp::confess() };
+$Data::Dumper::Indent = 1;
+
+my $apidef = "api.api";
+my $vars = {
+ package => '',
+ output => 'bin',
+ verbose => 0,
+};
+my @apilist;
+
+while (@ARGV) {
+ my $cmd = shift;
+
+ if ($cmd =~ m/^(-[^-])(.+)/) {
+ $cmd = $1;
+ unshift @ARGV, $2;
+ }
+
+ if ($cmd eq "-t") {
+ $vars->{package} = shift;
+ } elsif ($cmd eq "-d") {
+ $vars->{output} = shift;
+ } elsif ($cmd eq "-I") {
+ push @{$vars->{include}}, shift;
+ } elsif ($cmd eq "-v") {
+ $vars->{verbose}++;
+ } elsif ($cmd eq "-a") {
+ push @apilist, shift;
+ } else {
+ $apidef = $cmd;
+ }
+}
+
+push @{$vars->{include}}, "$FindBin::Bin/../lib";
+push @INC, dirname($apidef);
+
+print Dumper($vars) if $vars->{verbose};
+
+my $api = new api($apidef, $vars, @apilist);
+
+#print Dumper($api);
+
+exportLibraries($api);
+exportStructs($api);
+exportConstants($api);
+exit 0;
+
+sub formatFunction {
+ my $api = shift;
+ my $c = shift;
+ my $tname = shift;
+ my $template = shift;
+ my $info = new method($api, $c);
+
+ #print 'function='.Dumper($c);
+ #print 'members='.Dumper(\@members);
+ #print 'info='.Dumper($info);
+
+ my $code;
+
+ # allow per-function override
+ $template = $api->findTemplateName($c->{$tname}) if defined $c->{$tname};
+
+ if (defined($template)) {
+ if ($tname eq 'func:template') {
+ foreach my $l (split /\n/,Dumper($c)) {
+ $code .= "// $l\n";
+ }
+ }
+
+ $code .= code::applyTemplate($template, $info->{vars});
+ $code =~ s/^\s*\n//osgm;
+
+ return $code;
+ } else {
+ return ();
+ }
+}
+
+sub formatCall {
+ my $api = shift;
+ my $c = shift;
+ my $template = $api->{index}->{'code:method'};
+ my $upcall = api::findItem($template, 'upcall');
+ my $downcall = api::findItem($template, 'downcall');
+ my $info = new method($api, $c);
+
+ #print 'function='.Dumper($c);
+ #print 'members='.Dumper(\@members);
+
+ my $code;
+ foreach my $l (split /\n/,Dumper($info)) {
+ $code .= "//$l\n";
+ }
+
+ $info->{vars}->{downcall} = ($c->{access} =~ m/r/) ? code::applyTemplate($downcall, $info->{vars}) : '';
+ $info->{vars}->{upcall} = ($c->{access} =~ m/w/) ? code::applyTemplate($upcall, $info->{vars}) : '';
+
+ return $code.code::applyTemplate(api::findItem($api->{index}->{'code:class'}, 'call'), $info->{vars});
+}
+
+sub formatItems {
+ my $api = shift;
+ my $obj = shift;
+ my $inc = shift;
+ my $res = shift;
+ my @list;
+
+ @list = grep {
+ $api->{output}->{"$_->{type}:$_->{name}"}++;
+ $res->{seen}->{"$_->{type}:$_->{name}"}++ == 0
+ } $api->findMatches($inc, $res->{ctx});
+
+ if ($inc->{type} eq 'func' && $inc->{literal}) {
+ push @{$res->{func}}, $inc->{literal};
+ } elsif ($inc->{type} eq 'func') {
+ my $def = $api->{index}->{'func:<default>'};
+ my $func = api::optionValue('func:template', 'code:method=invoke', $inc, $res->{template}, $def);
+ my $init = api::optionValue('init:template', undef, $inc, $res->{template}, $def);
+ my $funct = $api->findTemplateName($func);
+ my $initt = $api->findTemplateName($init) if defined $init;
+
+ push @{$res->{func}}, map { formatFunction($api, $_, 'func:template', $funct) } @list;
+ push @{$res->{init}}, map { formatFunction($api, $_, 'init:template', $initt) } @list;
+ } elsif ($inc->{type} eq 'define') {
+ push @{$res->{define}}, map { code::formatDefine($api, $_) } @list;
+ } elsif ($inc->{type} eq 'enum') {
+ push @{$res->{enum}}, map { code::formatEnum($api, $_, $res->{seen}) } @list;
+ } elsif ($inc->{type} eq 'call') {
+ push @{$res->{call}}, map { formatCall($api, $_) } @list;
+ } else {
+ die;
+ }
+}
+
+sub formatLibrary {
+ my $api = shift;
+ my $obj = shift;
+ my $res = shift;
+ my $data = $api->{data};
+ my $d;
+
+ print "library $obj->{name}\n" if ($api->{vars}->{verbose} > 0);
+
+ foreach my $inc (@{$obj->{items}}) {
+ if ($inc->{type} eq 'library') {
+ $api->{output}->{"$inc->{match}"}++;
+ formatLibrary($api, $api->{index}->{$inc->{match}}, $res);
+ } elsif ($inc->{type} =~ m/^(func|call|define|enum)$/no) {
+ print " $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
+ formatItems($api, $obj, $inc, $res);
+ } elsif ($inc->{match} eq 'code:<inline>') {
+ print " $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
+ push @{$res->{func}}, $inc->{literal};
+ } elsif ($inc->{type} eq 'code') {
+ # apply template perhaps, or apply with set?
+ push @{$res->{func}}, map {
+ if (defined($inc->{options}->[0])) {
+ api::findItem($_, $inc->{options}->[0])->{literal};
+ } else {
+ $_->{literal}
+ }
+ } grep { $_->{match} =~ m/$inc->{regex}/ } @{$api->{api}};
+ } elsif ($inc->{type} ne 'field') {
+ print Dumper($inc);
+ die;
+ }
+ }
+
+ if (defined $obj->{imports}) {
+ my @x = map { "import $_;" } @{$obj->{imports}};
+ $res->{import} = \@x;
+ }
+}
+
+# TODO: embedded structs
+sub formatStruct {
+ my $api = shift;
+ my $obj = shift;
+ my $s = shift;
+ my $data = $api->{data};
+ my $seen = {};
+ my $structTemplate = $api->findTemplate($obj, $s);
+ my @members = code::scanFields($api, $s);
+ my @membersOutput = grep { $_->{field}->{output} } @members;
+
+ my $varhandles = join '', map { code::formatTemplate($_->{match}->{varhandle}, $_->{match}, "\t") } @membersOutput;
+ my $accessors = join "\n", map {
+ my ($m, $type, $match) = ($_->{field}, $_->{type}, $_->{match});
+ map {
+ my $tname = $_;
+ my $accessor = $api->{index}->{$tname};
+
+ map { code::applyTemplate($_, $match) } grep { $_ } map { api::findItem($accessor, $_) } map {
+ my @list = ();
+ if ($_ =~ m/r/o || $tname eq 'code:getbyvalue') {
+ push @list, 'get';
+ push @list, 'geti' if $_ =~ m/i/o;
+ }
+ if ($_ =~ m/w/o) {
+ push @list, 'set';
+ push @list, 'seti' if $_ =~ m/i/o;
+ }
+ @list;
+ } $m->{access};
+ } split(/,/,api::optionValue('template', undef, $type));
+ } @membersOutput;
+
+ my $res = {
+ ctx => $s,
+ library => [],
+ func => [],
+ init => [],
+ define => [],
+ enum => [],
+ call => [],
+ seen => {},
+ template => $structTemplate,
+ };
+
+ formatLibrary($api, $obj, $res);
+
+ # handle per-struct format functions
+ foreach my $fmt (@{$s->{postformat}}) {
+ $fmt->($api, $obj, $res, $s);
+ }
+
+ my $vars = {
+ %{$api->{vars}},
+ layout => code::formatStructLayout($api, $s, \@members),
+ rename => $s->{rename},
+ name => $s->{name},
+ varhandles => $varhandles,
+ accessors => $accessors,
+ init => join("\n", @{$res->{init}}),
+ methods => join("\n", @{$res->{func}}),
+ defines => join("\n", @{$res->{define}}),
+ enums => join("\n", @{$res->{enum}}),
+ };
+
+ my $code;
+ foreach my $l (split /\n/,Dumper({ template => $api->queryTemplate($obj, $s), struct => $s }, \@members)) {
+ $code .= "//$l\n";
+ }
+
+ $api->{output}->{"$s->{type}:$s->{name}"} = 1;
+
+ return $code.code::applyTemplate($structTemplate, $vars, 1);
+}
+
+# output all libraries
+sub exportLibraries {
+ my $api = shift;
+ my $data = $api->{data};
+ my $def = $api->{index}->{'library:<default>'};
+
+ foreach my $obj (grep { $_->{type} eq 'library' } @{$api->{api}}) {
+ next if $api->{output}->{"$obj->{type}:$obj->{name}"};
+ next if $obj->{output} != 1;
+
+ my $library = $api->findTemplateName(api::optionValue('template', 'code:class=library', $obj, $def));
+ my $res = {
+ ctx => $obj,
+ library => [],
+ import => [],
+ func => [],
+ init => [],
+ define => [],
+ enum => [],
+ call => [],
+ seen => {},
+ template => $library,
+ };
+
+ formatLibrary($api, $obj, $res);
+
+ my $vars = {
+ %{$api->{vars}},
+ name => $obj->{name},
+ imports => join("\n", @{$res->{import}}),
+ init => join("\n", @{$res->{init}}),
+ defines => join("\n", @{$res->{define}}),
+ enums => join("\n", @{$res->{enum}}),
+ funcs => join("\n", @{$res->{func}}),
+ calls => join("\n", @{$res->{call}}),
+ };
+
+ export($api, $obj->{name}, code::applyTemplate($library, $vars, 1));
+ }
+}
+
+sub export {
+ my $api = shift;
+ my $name = shift;
+ my $text = shift;
+
+ my $f = $api->openOutput($name);
+ print $f $text;
+ $api->closeOutput($name, $f);
+}
+
+sub formatClass {
+ my $api = shift;
+ my $obj = shift;
+ my $s = shift;
+
+ if ($s->{type} =~ m/struct|union/) {
+ return formatStruct($api, $obj, $s);
+ } elsif ($s->{type} eq 'call') {
+ return formatCall($api, $s);
+ } else {
+ die;
+ }
+}
+
+sub exportStructs {
+ my $api = shift;
+ my $data = $api->{data};
+
+ # first those directly referenced
+ foreach my $obj (grep { $_->{type} =~ m/call|struct|union/ } @{$api->{api}}) {
+ my @list = $api->findMatches($obj, $obj);
+
+ print "gen ".($#list+1)." $obj->{type} $obj->{name}\n" if ($api->{vars}->{verbose} > 0);
+ foreach my $s (@list) {
+ next if !$s->{output};
+ next if $api->{output}->{"$s->{type}:$s->{name}"};
+
+ print " $s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+ export($api, $s->{rename}, formatClass($api, $obj, $s));
+ }
+ }
+
+ # then anything else left using the default outputs
+ foreach my $s (grep { $_->{output} && ($_->{type} =~ m/struct|union|call/) && !$api->{output}->{"$_->{type}:$_->{name}"} } @{$api->{api}}) {
+ my $obj = $data->{"$s->{type}:<default>"};
+ print "* $s->{name}\n" if ($api->{vars}->{verbose} > 1);
+ export($api, $s->{rename}, formatClass($api, $obj, $s));
+ }
+}
+
+# exports any define/enum not yet included elsehwere
+# TODO: this is sort of not very useful
+sub exportConstants {
+ my $api = shift;
+ my $data = $api->{data};
+ my $template = $api->{index}->{'code:class'};
+ my $constant = api::findItem($template, 'constants');
+
+ # hmm, not sure if i should use obj or just the values directly here
+ foreach my $s (grep { $_->{type} =~ m/define|enum/ } values %{$api->{data}}) {
+ next if $api->{output}->{"$s->{type}:$s->{name}"};
+ next if !$s->{output};
+
+ my $defines = '';
+ my $enums = '';
+
+ $defines = code::formatDefine($api, $s) if $s->{type} eq 'define';
+ $enums = code::formatEnum($api, $s) if $s->{type} eq 'enum';
+
+ my $vars = {
+ %{$api->{vars}},
+ name => $s->{name},
+ defines => $defines,
+ enums => $enums,
+ };
+
+ my $code;
+ foreach my $l (split /\n/,Dumper($s)) {
+ $code .= "//$l\n";
+ }
+
+ export($api, $s->{name}, $code.code::applyTemplate($constant, $vars));
+ }
+}
--- /dev/null
+
+package au.notzed.nativez;
+
+public interface Array<T> {
+ long length();
+ T getAtIndex(long i);
+ boolean isEmpty();
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class ByteArray extends AbstractList<Byte> implements Pointer {
+ 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(MemoryAddress address, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, 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 String toUtf8String() {
+ return segment.getUtf8String(0L);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class DoubleArray extends AbstractList<Double> implements Pointer {
+ final MemorySegment segment;
+
+ private DoubleArray(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ public static DoubleArray create(MemorySegment segment) {
+ return new DoubleArray(segment);
+ }
+
+ public static DoubleArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, length * Memory.DOUBLE.byteSize(), scope));
+ }
+
+ public static DoubleArray createArray(MemoryAddress address, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+ }
+
+ public static DoubleArray createArray(long length, SegmentAllocator alloc) {
+ return create(alloc.allocateArray(Memory.DOUBLE, length));
+ }
+
+ public static DoubleArray create(SegmentAllocator alloc, double... values) {
+ return create(alloc.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);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class FloatArray extends AbstractList<Float> implements Pointer {
+ final MemorySegment segment;
+
+ private FloatArray(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ public static FloatArray create(MemorySegment segment) {
+ return new FloatArray(segment);
+ }
+
+ public static FloatArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, length * Memory.FLOAT.byteSize(), scope));
+ }
+
+ public static FloatArray createArray(MemoryAddress address, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+ }
+
+ public static FloatArray createArray(long length, SegmentAllocator alloc) {
+ return create(alloc.allocateArray(Memory.FLOAT, length));
+ }
+
+ public static FloatArray create(SegmentAllocator alloc, float... values) {
+ return create(alloc.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);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+import static jdk.incubator.foreign.ValueLayout.OfAddress;
+
+/**
+ * This is a per-thread stack-based allocator.
+ * <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 quite a bit faster than using an arena allocator ... but it also
+ * removes most scope guarantees.
+ */
+public class Frame implements AutoCloseable, SegmentAllocator {
+
+ private final ResourceScope scope;
+ private final SegmentAllocator alloc;
+
+ Frame() {
+ this.scope = ResourceScope.newConfinedScope();
+ this.alloc = SegmentAllocator.newNativeArena(scope);
+ }
+
+ public static Frame frame() {
+ return new Frame();
+ }
+
+ public ResourceScope scope() {
+ return scope;
+ }
+
+ @Override
+ public MemorySegment allocate(long size, long alignment) {
+ return alloc.allocate(size, alignment);
+ }
+
+ @Override
+ public void close() {
+ scope.close();
+ }
+
+ // private final long tos;
+ // private Stack stack;
+ // private ResourceScope overflow;
+
+ // Frame(long tos, Stack stack) {
+ // this.tos = tos;
+ // this.stack = stack;
+ // }
+
+ // private static final ResourceScope scope = ResourceScope.newSharedScope(Cleaner.create());
+ // private static final ThreadLocal<Stack> stacks = ThreadLocal.withInitial(() -> new Stack(scope));
+
+ // public static Frame frame() {
+ // //return stacks.get().createFrame();
+ // }
+
+ // private 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(sp, this);
+ // }
+ // }
+
+ // @Override
+ // public MemorySegment allocate(long size, long alignment) {
+ // if (stack.thread != Thread.currentThread())
+ // throw new IllegalStateException();
+ // if (alignment != Long.highestOneBit(alignment))
+ // throw new IllegalArgumentException();
+ // if (stack.sp >= size) {
+ // stack.sp = (stack.sp - size) & ~(alignment - 1);
+ // return stack.stack.asSlice(stack.sp, size).fill((byte)0);
+ // } else {
+ // // or arena allocator?
+ // if (overflow == null)
+ // overflow = ResourceScope.newConfinedScope();
+ // return MemorySegment.allocateNative(size, alignment, overflow);
+ // }
+ // }
+
+ // @Override
+ // public void close() {
+ // stack.sp = tos;
+ // stack = null;
+ // if (overflow != null) {
+ // overflow.close();
+ // overflow = null;
+ // }
+ // }
+
+ public MemorySegment allocateInt() {
+ return allocate(Memory.INT);
+ }
+
+ public MemorySegment allocateInt(int count) {
+ return allocate(Memory.INT, count);
+ }
+
+ public MemorySegment allocateLong() {
+ return allocate(Memory.LONG);
+ }
+
+ public MemorySegment allocateLong(int count) {
+ return allocateArray(Memory.LONG, count);
+ }
+
+ public MemorySegment allocatePointer() {
+ return allocate(Memory.POINTER);
+ }
+
+ public MemorySegment allocatePointer(int count) {
+ return allocateArray(Memory.POINTER, count);
+ }
+
+ public 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;
+ }
+
+ public MemorySegment copy(byte value) {
+ return allocate(Memory.BYTE, value);
+ }
+
+ public MemorySegment copy(short value) {
+ return allocate(Memory.SHORT, value);
+ }
+
+ public MemorySegment copy(int value) {
+ return allocate(Memory.INT, value);
+ }
+
+ public MemorySegment copy(long value) {
+ return allocate(Memory.LONG, value);
+ }
+
+ public MemorySegment copy(float value) {
+ return allocate(Memory.FLOAT, value);
+ }
+
+ public MemorySegment copy(double value) {
+ return allocate(Memory.DOUBLE, value);
+ }
+
+ public MemorySegment copy(byte[] value) {
+ return allocateArray(Memory.BYTE, value);
+ }
+
+ public MemorySegment copy(int[] value) {
+ return allocateArray(Memory.INT, value);
+ }
+
+ public MemorySegment copy(long[] value) {
+ return allocateArray(Memory.LONG, value);
+ }
+
+ public MemorySegment copy(float[] value) {
+ return allocateArray(Memory.FLOAT, value);
+ }
+
+ public MemorySegment copy(String value) {
+ return allocateUtf8String(value);
+ }
+
+ /*
+ public <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;
+ }
+
+ public <T extends Native> MemorySegment copy(T value) {
+ return copy(value.address());
+ }
+
+ public <T extends Native> MemorySegment copy(MemoryAddress value) {
+ MemorySegment mem = allocateAddress();
+ MemoryAccess.setAddress(mem, value);
+ return mem;
+ }
+ */
+ // create an array pointing to strings
+ public 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;
+ }
+ }
+
+ public static MemorySegment copy(MemorySegment ctx, String string) {
+ if (string != null) {
+ SegmentAllocator alloc = SegmentAllocator.nativeAllocator(ctx.scope());
+
+ return alloc.allocateUtf8String(string);
+ } else {
+ return Memory.NULL;
+ }
+ }
+
+ public static MemorySegment copy(MemorySegment ctx, String[] array) {
+ System.out.printf("copy array %s\n", array != null ? array.length : "");
+ if (array != null) {
+ SegmentAllocator alloc = SegmentAllocator.nativeAllocator(ctx.scope());
+ MemorySegment list = alloc.allocateArray(Memory.POINTER, array.length);
+ for (int i = 0; i < array.length; i++) {
+ System.out.printf(" [%d] '%s'\n", i, array[i]);
+ list.setAtIndex(Memory.POINTER, i, alloc.allocateUtf8String(array[i]));
+ }
+ return list;
+ } else {
+ return Memory.NULL;
+ }
+ }
+}
--- /dev/null
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.NativeSymbol;
+
+public record FunctionPointer<T>(NativeSymbol symbol, T function) {
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+/**
+ * A HandleArray is a typed PointerArray
+ * It's flexible and can be used to represent multiple levels of typed arrays, etc.
+ *
+ * The supplied ResourceScope is used for any retrieved entries.
+ */
+public class HandleArray<T extends Pointer> extends AbstractList<T> implements Pointer {
+ public final MemorySegment segment;
+ final ResourceScope scope;
+ BiFunction<MemoryAddress,ResourceScope,T> create;
+
+ private HandleArray(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+ this.segment = segment;
+ this.create = create;
+ this.scope = scope;
+ }
+
+ public static <T extends Pointer> HandleArray<T> create(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create) {
+ return new HandleArray<>(segment, create, segment.scope());
+ }
+
+ public static <T extends Pointer> HandleArray<T> create(MemorySegment segment, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+ return new HandleArray<>(segment, create, scope);
+ }
+
+ public static <T extends Pointer> HandleArray<T> createArray(long size, SegmentAllocator alloc, BiFunction<MemoryAddress,ResourceScope,T> create) {
+ return create(alloc.allocateArray(Memory.POINTER, size), create);
+ }
+
+ public static <T extends Pointer> HandleArray<T> createArray(long size, SegmentAllocator alloc, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+ return create(alloc.allocateArray(Memory.POINTER, size), create, scope);
+ }
+
+ public static <T extends Pointer> HandleArray<T> createArray(MemoryAddress address, long size, BiFunction<MemoryAddress,ResourceScope,T> create, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, size * Memory.POINTER.byteSize(), scope), create);
+ }
+
+ public static <T extends Pointer> HandleArray<T> create(SegmentAllocator alloc, BiFunction<MemoryAddress,ResourceScope,T> create, T... values) {
+ HandleArray<T> array = create(alloc.allocateArray(Memory.POINTER, values.length), create);
+ for (int i=0;i<values.length;i++)
+ array.setAtIndex(i, values[i]);
+ return array;
+ }
+
+ public HandleArray<T> asSlice(long offset) {
+ return create(segment.asSlice(offset * Memory.POINTER.byteSize()), create, scope);
+ }
+
+ public HandleArray<T> asSlice(long offset, long length) {
+ return create(segment.asSlice(offset * Memory.POINTER.byteSize(), length * Memory.POINTER.byteSize()), create, scope);
+ }
+
+ @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) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class IntArray extends AbstractList<Integer> implements Pointer {
+
+ final MemorySegment segment;
+
+ private IntArray(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ public static IntArray create(MemorySegment segment) {
+ return new IntArray(segment);
+ }
+
+ public static IntArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, length * Memory.INT.byteSize(), scope));
+ }
+
+ public static IntArray createArray(MemoryAddress address, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+ }
+
+ public static IntArray createArray(long length, SegmentAllocator alloc) {
+ return create(alloc.allocateArray(Memory.INT, length));
+ }
+
+ public static IntArray create(SegmentAllocator alloc, int... values) {
+ return create(alloc.allocateArray(Memory.INT, values));
+ }
+
+ public IntArray asSlice(long offset) {
+ return create(segment.asSlice(offset * Memory.INT.byteSize()));
+ }
+
+ public IntArray asSlice(long offset, long length) {
+ return create(segment.asSlice(offset * Memory.INT.byteSize(), length * Memory.INT.byteSize()));
+ }
+
+ public final MemorySegment segment() {
+ return segment;
+ }
+
+ 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);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class LongArray extends AbstractList<Long> implements Pointer {
+ final MemorySegment segment;
+
+ public LongArray(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ public static LongArray create(MemorySegment segment) {
+ return new LongArray(segment);
+ }
+
+ public static LongArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, length * Memory.LONG.byteSize(), scope));
+ }
+
+ public static LongArray createArray(long length, SegmentAllocator alloc) {
+ return create(alloc.allocateArray(Memory.LONG, length));
+ }
+
+ public static LongArray create(SegmentAllocator alloc, long... values) {
+ return create(alloc.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);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+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;
+import java.util.List;
+
+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?
+ // Note: currently can't create zero-length segments
+ static final MemorySegment NULL = MemorySegment.ofAddress(MemoryAddress.NULL, 1, ResourceScope.globalScope());
+
+ public static ResourceScope sharedScope() {
+ return sharedScope;
+ }
+
+ public static MethodHandle downcall(FunctionDescriptor desc) {
+ return CLinker.systemCLinker().downcallHandle(desc);
+ }
+
+ 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;
+ }
+
+ public static MethodHandle downcall(String name, FunctionDescriptor desc, Function<String, MemoryAddress> resolve, ResourceScope scope) {
+ MemoryAddress sym = resolve.apply(name);
+ 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(MethodHandles.Lookup lookup, Object instance, String signature, FunctionDescriptor desc, ResourceScope scope) {
+ try {
+ java.lang.reflect.Method m = instance.getClass().getMethods()[0];
+ MethodHandle handle = lookup.findVirtual(instance.getClass(), m.getName(), MethodType.fromMethodDescriptorString(signature, Memory.class.getClassLoader()))
+ .bindTo(instance);
+ return CLinker.systemCLinker().upcallStub(handle, desc, scope);
+ } catch (Throwable t) {
+ throw new AssertionError(t);
+ }
+ }
+
+ public static NativeSymbol upcall(MethodHandles.Lookup lookup, 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);
+ }
+ }
+
+ public static Addressable address(Addressable v) {
+ return v != null ? v.address() : MemoryAddress.NULL;
+ }
+
+ public static Addressable address(Pointer v) {
+ return v != null ? v.address() : MemoryAddress.NULL;
+ }
+
+ public static <T> Addressable address(FunctionPointer<T> v) {
+ return v != null ? v.symbol().address() : MemoryAddress.NULL;
+ }
+
+ //public static long length(Pointer v) {
+ // return v != null ? v.length() : 0;
+ //}
+ public static long length(List<?> list) {
+ return list != null ? list.size() : 0;
+ }
+
+ public static long length(Array<?> list) {
+ return list != null ? list.length() : 0;
+ }
+
+ public static long length(MemorySegment list) {
+ return list != null ? list.byteSize() : 0;
+ }
+
+ public static long length(Object[] list) {
+ return list != null ? list.length : 0;
+ }
+
+ public static long size(MemorySegment s) {
+ return s != null ? s.byteSize() : 0;
+ }
+
+ public static String copyString(MemoryAddress string) {
+ return string != MemoryAddress.NULL ? string.getUtf8String(0) : null;
+ }
+
+ public static MemorySegment copyString(String string, SegmentAllocator alloc) {
+ return (string != null) ? alloc.allocateUtf8String(string) : Memory.NULL;
+ }
+
+ public static String[] copyStringArray(MemoryAddress list, long len) {
+ if (list != MemoryAddress.NULL) {
+ String[] array = new String[(int)len];
+ for (int i = 0; i < array.length; i++) {
+ MemoryAddress fu = list.getAtIndex(Memory.POINTER, i);
+ array[i] = fu != MemoryAddress.NULL ? fu.getUtf8String(0) : null;
+ }
+ return array;
+ } else {
+ return null;
+ }
+ }
+
+ public static MemorySegment copyStringArray(String[] array, SegmentAllocator alloc) {
+ if (array != null) {
+ MemorySegment list = alloc.allocateArray(Memory.POINTER, array.length);
+ for (int i = 0; i < array.length; i++) {
+ list.setAtIndex(Memory.POINTER, i, array[i] != null ? alloc.allocateUtf8String(array[i]) : MemoryAddress.NULL);
+ }
+ return list;
+ } else {
+ return Memory.NULL;
+ }
+ }
+
+ static void format(StringBuilder sb, GroupLayout layout, MemorySegment s, String prefix) {
+ try {
+ sb.append(prefix).append(layout.name().orElse("?")).append(" {\n");
+ for (MemoryLayout m: layout.memberLayouts()) {
+ if (m instanceof ValueLayout l) {
+ var vh = layout.varHandle(MemoryLayout.PathElement.groupElement(l.name().get()));
+ sb.append(prefix).append(" ").append(l.name().get()).append(": ").append(vh.get(s)).append("\n");
+ } else if (m instanceof GroupLayout l) {
+ var sh = layout.sliceHandle(MemoryLayout.PathElement.groupElement(l.name().get()));
+ var g = (MemorySegment)sh.invokeExact(s);
+
+ //sb.append(prefix).append(l.name().get()).append(": {\n");
+ format(sb, l, g, prefix + " ");
+ //sb.append(prefix).append("}\n");
+ } else if (m instanceof jdk.incubator.foreign.SequenceLayout l) {
+ var sh = layout.sliceHandle(MemoryLayout.PathElement.groupElement(l.name().get()));
+ var g = (MemorySegment)sh.invokeExact(s);
+
+ //sb.append(prefix).append(l.name().get()).append(": {\n");
+ format(sb, l, g, prefix + " ");
+ //sb.append(prefix).append("}\n");
+ }
+ }
+ sb.append(prefix).append("}\n");
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ static void format(StringBuilder sb, SequenceLayout layout, MemorySegment s, String prefix) {
+ sb.append(prefix).append(layout.name().orElse("?")).append(" [\n");
+ layout.elementCount().ifPresent(len -> {
+ try {
+ MemoryLayout el = layout.elementLayout();
+
+ if (el instanceof ValueLayout l) {
+ var vh = layout.varHandle(MemoryLayout.PathElement.sequenceElement());
+
+ for (long i = 0; i < len; i++) {
+ sb.append(prefix).append(" ").append(vh.get(s, i)).append("\n");
+ }
+ } else if (el instanceof GroupLayout l) {
+ MethodHandle eh = layout.sliceHandle(MemoryLayout.PathElement.sequenceElement());
+
+ for (long i = 0; i < len; i++) {
+ MemorySegment e = (MemorySegment)eh.invokeExact(s, i);
+ format(sb, l, e, prefix + " ");
+ }
+ } else if (el instanceof SequenceLayout l) {
+ MethodHandle eh = layout.sliceHandle(MemoryLayout.PathElement.sequenceElement());
+
+ for (long i = 0; i < len; i++) {
+ MemorySegment e = (MemorySegment)eh.invokeExact(s, i);
+ format(sb, l, e, prefix + " ");
+ }
+ }
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ }
+ });
+ sb.append(prefix).append("]\n");
+ }
+
+ public static String toString(MemorySegment s, GroupLayout layout) {
+ StringBuilder sb = new StringBuilder();
+
+ long len = s.byteSize() / layout.byteSize();
+ if (len > 1) {
+ format(sb, MemoryLayout.sequenceLayout(len, layout).withName("array[]"), s, "");
+ } else {
+ format(sb, layout, s, "");
+ }
+ return sb.toString();
+ }
+}
--- /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 au.notzed.nativez;
+
+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 Pointer {
+
+ 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;
+ }
+
+ public ResourceScope scope() {
+ return ResourceScope.globalScope();
+ }
+
+ /* ********************************************************************** */
+ /* 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(Class<T> jtype, 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 = jtype.cast(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;
+ }
+ }
+}
+++ /dev/null
-/*
- * Copyright (C) 2018 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * (1) Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * (2) Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * (3)The name of the author may not be used to
- * endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-package au.notzed.nativez;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Native object manager.
- * <p>
- */
-public abstract class NativeZ {
-
- private final static boolean dolog = false;
-
- private static Logger log() {
- return Logger.getLogger("notzed.nativez");
- }
-
- /**
- * Globally system unique key to a native resource.
- * <p>
- * This will typically be a C pointer.
- */
- final long p;
-
- /**
- * Resource index.
- */
- static private final PointerTable map = new PointerTable();
-
- /**
- * Reference queue for stale objects.
- */
- static private final ReferenceQueue<NativeZ> references = new ReferenceQueue<>();
-
- static {
- Thread cleanup = new Thread(NativeZ::cleaner, "NativeZ cleaner");
- cleanup.setPriority(Thread.MAX_PRIORITY);
- cleanup.setDaemon(true);
- cleanup.start();
- }
-
- /**
- * Create a new object.
- *
- * @param p
- */
- protected NativeZ(long p) {
- this.p = p;
- }
-
- /**
- * Retrieve the resource key.
- *
- * @return
- */
- public long getP() {
- return p;
- }
-
- /**
- * Natives are considered equal if their p values are equal.
- *
- * @param obj
- * @return
- */
- @Override
- public boolean equals(Object obj) {
- return (obj instanceof NativeZ) && p == ((NativeZ) obj).p;
- }
-
- @Override
- public int hashCode() {
- return CHandle.hashCode(p);
- }
-
- /**
- * Internally create a new instance.
- * <p>
- * The module must be open to notzed.nativez for this to work.
- *
- * @param <T>
- * @param jtype Type of object to create.
- * @param p The reference handle.
- * @return A newly created object.
- * @throws RuntimeException if the object cannot be instantiated.
- */
- private static <T extends NativeZ> T createInstance(Class<T> jtype, long p) {
- cleanerStep();
- try {
- Class[] params = {Long.TYPE};
- Constructor<T> cc = jtype.getDeclaredConstructor(params);
-
- cc.setAccessible(true);
-
- return cc.newInstance(p);
- } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
- log().log(Level.SEVERE, null, ex);
- throw new RuntimeException(ex);
- }
- }
-
- /**
- * Register a new managed resource.
- * <p>
- * Use this function if the object does not use the default constructor
- * or has been created separately.
- * <p>
- * Do not use this for internally reference counted resources.
- *
- * @param <T>
- * @param o The object to register.
- * @return o is returned.
- */
- public static <T extends NativeZ> T register(T o) {
- CHandle h = new CResourceHandle(o, references, o.p);
-
- synchronized (map) {
- map.putAlways(h);
- }
-
- return o;
- }
-
- /**
- * Create a new managed object for a resource.
- * <p>
- * Use this function when possible if using the default constructor
- * as it saves a native to managed transition and also allows
- * for concurrent instantiation.
- * <p>
- * Do not call this if the resource may have already been registered or
- * if it is internally reference counted, use {@link resolve} instead.
- *
- * @param jtype managed object type.
- * @param p unique resource key.
- * @return A newly created object.
- * @throws RuntimeException if the object cannot be instantiated.
- */
- public static <T extends NativeZ> T create(Class<T> jtype, long p) {
- T o;
- CHandle h;
-
- if (dolog)
- log().fine(() -> String.format(" create $%016x %s", p, jtype.getName()));
-
- o = createInstance(jtype, p);
- h = new CResourceHandle(o, references, p);
-
- synchronized (map) {
- map.putAlways(h);
- }
-
- return o;
- }
-
- /**
- * Create a new or resolve an existing managed object for a resource.
- * <p>
- * If the object already exists it is simply returned otherwise
- * a new instance is created and registered. It is safe to call this
- * instead of {@link create} for all cases.
- * <p>
- * If the resources are managed natively via reference counting then this
- * function must be used to ensure a unique instance is created.
- * <p>
- * This may be called on the same resource key via multiple threads and
- * is internally synchronised.
- *
- * @param jtype type of object to createInstance.
- * @param p native pointer (or other native key).
- * @return A unique Java object which manages the native resource.
- */
- public static <T extends NativeZ> T resolve(Class<T> jtype, long p) {
- T o;
-
- if (dolog)
- log().fine(() -> String.format(" resolve $%016x %s", p, 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 CResourceHandle(o, references, p);
- map.putAlways(h);
- }
- }
- return o;
- }
-
- /**
- * Create a non-managed object for a resource.
- * <p>
- * This is a way to access internal members and other data which is
- * not allocated explicitly.
- * <p>
- * It is up to the caller to ensure the reference remains valid while in
- * use, for example by retaining a reference to it's container.
- * <p>
- * This may be called on the same resource key via multiple threads and
- * is internally synchronised.
- *
- * @param jtype object type
- * @param p unqiue resource key. As this must be globaly unique it
- * cannot reference embedded structures that begin at the start of
- * a container. A workaround is to offset the value and account for
- * that in the implementation.
- * @return A unique Java object for accessing the native resource.
- */
- public static <T extends NativeZ> T refer(Class<T> jtype, long p) {
- T o;
-
- if (dolog)
- log().fine(() -> String.format(" refer $%016x %s", p, jtype.getName()));
-
- synchronized (map) {
- CHandle h = (CHandle) map.get(p);
-
- if (h == null || (o = jtype.cast(h.get())) == null) {
- o = createInstance(jtype, p);
- h = new CReferenceHandle(o, references, p);
- map.putAlways(h);
- }
- }
-
- return o;
- }
-
- /**
- * Explicitly release a resource.
- * <p>
- * The object is advanced to the refernce queue immediately.
- * <p>
- * Applications MUST NOT access this object afterwards, even if
- * they retain references to it.
- */
- public void release() {
- WeakReference<? extends NativeZ> ref;
-
- synchronized (map) {
- ref = map.remove(p);
- }
-
- if (ref != null) {
- if (dolog)
- log().fine(() -> String.format(" force $%016x %s", p, getClass().getName()));
-
- ref.enqueue();
- }
- }
-
- /**
- * Explicitly release multiple resources.
- * <p>
- * A convenience function to release multiple resources explicitly.
- * <p>
- * null objects are safely ignored.
- *
- * @param list
- */
- public static void release(NativeZ... list) {
- for (NativeZ o : list) {
- if (o != null) {
- o.release();
- }
- }
- }
-
- @Override
- public String toString() {
- return String.format("[$%016x: %s]", p, getClass().getSimpleName());
- }
-
- 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().info("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) {
- }
- }
-
- /**
- * Base class for a reference handle.
- * <p>
- * This is a {@link WeakReference} that maintains the hashtable bucket
- * chain and enough information to release the resource without referencing
- * it.
- */
- abstract private static class CHandle extends WeakReference<NativeZ> {
-
- protected long p;
- final Class<? extends NativeZ> jtype;
- CHandle next;
-
- public CHandle(NativeZ referent, ReferenceQueue<NativeZ> references, long p) {
- super(referent, references);
- this.p = p;
- this.jtype = referent.getClass();
- }
-
- /**
- * Actually release the native resource.
- */
- abstract void release();
-
- @Override
- public boolean equals(Object obj) {
- return (obj instanceof CHandle) && ((CHandle) obj).p == p;
- }
-
- @Override
- public int 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;
- }
- }
-
- /**
- * A dummy reference handle.
- * <p>
- * The {@link release} function simply clears the pointer.
- *
- * @todo This may not be needed as it's only purpose is to allow
- * unique global references across managed and unmanaged resources and
- * multiple instances of unmanaged objects are safe. However, jjmpeg has
- * some strange cases so it needs confirming.
- */
- private static class CReferenceHandle extends CHandle {
-
- public CReferenceHandle(NativeZ referent, ReferenceQueue<NativeZ> references, long p) {
- super(referent, references, p);
- }
-
- @Override
- void release() {
- if (dolog)
- log().fine(() -> String.format(" %016x: Ignoring release %s", p, jtype.getSimpleName()));
- p = 0;
- }
- }
-
- /**
- * A CResourceHandle is a weak reference that maintains enough
- * non-object-referring data to be able to release native resources.
- * <p>
- * It does this by saving the native pointer `p' and the
- * object class which manages it. A static method `void release(long)'
- * on the specific class is used to release the native resource when
- * it is no longer reachable or has been explicitly released.
- */
- private static class CResourceHandle extends CHandle {
-
- public CResourceHandle(NativeZ referent, ReferenceQueue<NativeZ> references, long p) {
- super(referent, references, p);
- }
-
- @Override
- void release() {
- try {
- if (p != 0) {
- if (dolog)
- log().fine(() -> String.format(" reles $%016x %s", p, jtype.getName()));
-
- Method mm = jtype.getDeclaredMethod("release", Long.TYPE);
- mm.setAccessible(true);
- mm.invoke(null, p);
- }
- } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
- if (dolog)
- log().log(Level.SEVERE, jtype.getName(), ex);
- ex.printStackTrace(System.out);
- } finally {
- p = 0;
- }
- }
- }
-
- /**
- * 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 * 2)
- resize(table.length * 2);
-
- int i = h.hashCode() & mask;
-
- h.next = table[i];
- table[i] = h;
- size += 1;
- }
-
- public CHandle get(long p) {
- int i = CHandle.hashCode(p) & mask;
- CHandle h = table[i];
-
- while (h != null && h.p != p)
- h = h.next;
- return h;
- }
-
- public CHandle remove(long p) {
- int i = CHandle.hashCode(p) & mask;
- CHandle h = table[i];
- CHandle a = null;
-
- while (h != null && h.p != 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;
- }
- }
-}
--- /dev/null
+
+package au.notzed.nativez;
+
+import jdk.incubator.foreign.MemoryAddress;
+import jdk.incubator.foreign.ResourceScope;
+
+/**
+ * Because you can't implement foriegn.Addressable for some silly reason
+ */
+public interface Pointer {
+ MemoryAddress address();
+ default ResourceScope scope() {
+ return ResourceScope.globalScope();
+ }
+ //default long length() {
+ // return 1;
+ //}
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class PointerArray extends AbstractList<MemoryAddress> implements Pointer {
+ final MemorySegment segment;
+
+ private PointerArray(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ public static PointerArray create(MemorySegment segment) {
+ return new PointerArray(segment);
+ }
+
+ public static PointerArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, length, scope));
+ }
+
+ public static PointerArray createArray(long length, SegmentAllocator alloc) {
+ return create(alloc.allocateArray(Memory.POINTER, length));
+ }
+
+ public static PointerArray create(Frame alloc, MemoryAddress... values) {
+ return create(alloc.allocateArray(Memory.POINTER, values));
+ }
+
+ 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);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2022 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 au.notzed.nativez;
+
+import jdk.incubator.foreign.*;
+
+import java.util.AbstractList;
+import java.util.function.Function;
+import java.util.function.BiFunction;
+import java.util.List;
+
+public class ShortArray extends AbstractList<Short> implements Pointer {
+ final MemorySegment segment;
+
+ private ShortArray(MemorySegment segment) {
+ this.segment = segment;
+ }
+
+ public static ShortArray create(MemorySegment segment) {
+ return new ShortArray(segment);
+ }
+
+ public static ShortArray createArray(MemoryAddress address, long length, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, length * Memory.SHORT.byteSize(), scope));
+ }
+
+ public static ShortArray createArray(MemoryAddress address, ResourceScope scope) {
+ return create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope));
+ }
+
+ public static ShortArray createArray(long length, SegmentAllocator alloc) {
+ return create(alloc.allocateArray(Memory.SHORT, length));
+ }
+
+ public static ShortArray create(SegmentAllocator alloc, short... values) {
+ return create(alloc.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);
+ }
+}
module notzed.nativez {
- requires java.logging;
-
+ requires transitive jdk.incubator.foreign;
exports au.notzed.nativez;
}
+++ /dev/null
-
-notzed.nativez_JNI_LIBRARIES = nativez
-
-nativez_SOURCES = nativez-jni.c nativez-$(TARGET:-amd64=).c
-nativez_CPPFLAGS = -I$(notzed.nativez_jnidir)
-nativez_CFLAGS = -Wmissing-prototypes
-nativez_HEADERS = nativez.h
-nativez_DEFS = nativez-jni.def
-
-nativez_makedep=$(notzed.nativez_objdir)/$(1).o: $(notzed.nativez_jnidir)/$(1).h
-
-$(foreach def,$(nativez_DEFS),$(eval $(call nativez_makedep,$(def:.def=))))
-
-$(notzed.nativez_jnidir)/%.h: src/notzed.nativez/jni/%.def src/notzed.nativez/jni/nativez-gen
- @install -d $(@D)
- src/notzed.nativez/jni/nativez-gen -J $< > $@ || ( rm $@ ; exit 1)
-
-# include tool in jmod
-nativez_COMMANDS=nativez-gen
-$(notzed.nativez_bindir)/nativez-gen: src/notzed.nativez/jni/nativez-gen
- install -DC $< $@
+++ /dev/null
-#!/usr/bin/perl
-
-# -*- Mode:perl; perl-indent-level:4;tab-width:4; -*-
-
-# usage [ options ]* def-file.def
-
-# -b basedir Base directory for any 'header' sections. It is added to the include path.
-# --func-name Function table variable name. Default is `fn'.
-# --java-name Java table variable name. Default is 'java'.
-# -J Create #defines to map java names to the variable name.
-# --java-long Create long java names for object types. Sort of like mangled jni names.
-
-# All other options and arguments are passed to cproto.
-
-$args = "$0 ".join " ", @ARGV;
-$java_name = "java";
-$func_name = "fn";
-$doJ = 0;
-$doLong = 0;
-$cprotoArgs = "";
-
-while ($#ARGV > 0) {
- my $cmd = shift;
- if ($cmd eq "-b") {
- $basedir = shift;
- $cprotoArgs .= " -I '${basedir}'";
- } elsif ($cmd eq "--func-name") {
- $func_name = shift;
- } elsif ($cmd eq "--java-name") {
- $java_name = shift;
- } elsif ($cmd eq "-J") {
- $doJ = 1;
- } elsif ($cmd eq "--java-long") {
- $doLong = 1;
- } else {
- $cprotoArgs .= " '$cmd'";
- }
-}
-$def = shift;
-
-$header = "";
-$librart = "";
-@functions = ();
-@headers = ();
-%proto = ();
-$last_library = "";
-$mode = "";
-
-open (IN,"<$def") || die ("opening $def");
-
-while (<IN>) {
- chop;
- s/#.*$//;
- if (m/^header (.*) (.*) \{/) {
- $library = $1;
- $header = $2;
- push @headers, $header;
-
- print STDERR "cproto -q -x ${cprotoArgs} $basedir/$header\n";
- %proto = ();
- open (PROTO, "cproto -q -x ${cprotoArgs} $basedir/$header|") || die("cproto failed");
-
- while (<PROTO>) {
- chop;
- $cproto = $_;
- if (m/([a-zA-Z0-9_]*)\(/) {
- $func = $1;
- $proto{$func} = $cproto;
- }
- }
- close PROTO;
-
- if ($library ne $last_library) {
- push @functions, "#$library";
- $last_library = $library;
- }
- $mode = "proto";
- } elsif (m/^java (.*) (.*) \{/) {
- $name = $1;
- $class = $2;
- $mode = "java";
- push @classes,"#$name:$class";
- } elsif (m/^}$/) {
- $mode = "";
- } elsif ($mode eq "proto") {
- if (m/\s*([a-zA-Z0-9_]+)/) {
- my $func= $1;
- my $cproto = $proto{$func};
-
- die ("No function $func in $header") if !defined($cproto);
-
- push @functions, $cproto;
- }
- } elsif ($mode eq "java") {
- # maps to strict "[static|],name,(arguments)"
- if (m/(static)? *([\w<>]*) *, *([\[\w\$<>\(\)\/;]*)/) {
- push @classes,"$1,$2,$3";
- }
- }
-}
-close IN;
-
-print "/* This file was autogenerated by: */\n";
-print "/* $args */\n";
-
-if ($#headers >= 0) {
- # Handle C prototype mappings
- foreach $h (@headers) {
- print "#include <$h>\n";
- }
-
- print "static struct functable {\n";
- foreach $func (@functions) {
- if ($func =~ m/^\#(.+)/) {
- print "\t/* lib$1 */\n";
- } else {
- $dfunc = $func;
- $dfunc =~ s/([a-zA-Z0-9_]*)(\(.*;)/(*\1)\2/;
- print "\t$dfunc\n";
- }
- }
- print "} ${func_name};\n";
- print "static const char *${func_name}_names =\n";
- foreach $func (@functions) {
- if ($func =~ m/^(\#.+)/) {
- print "\t\"$func\\0\"\n";
- } else {
- $func =~ m/([a-zA-Z0-9_]*)\(/;
- print "\t\"$1\\0\"\n";
- }
- }
- print "\t;\n";
-}
-
-if ($#classes >= 0) {
- # Handle java defines
- $name = "";
- $class = "";
- print "static struct {\n";
- foreach $func (@classes) {
- if ($func =~ m/^#(.+):(.+)/) {
- $name = $1;
- $class = $2;
- print "\t// $class\n";
- print "\tjclass $name"."_classid;\n";
- printf "#define $name"."_classid ${java_name}.$name"."_classid\n" if $doJ;
- } elsif ($func =~ m/(.*),(.+),\((.*)\).*/) {
- my $method = $2;
- my $args = $3;
-
- if ($doLong) {
- while ($args =~ m/(.*)L([^;]*);(.*)/) {
- my $a = $1;
- my $b = $2;
- my $c = $3;
-
- $b =~ s,/,_,g;
- $args = $a."_".$b."_".$c;
- }
- } else {
- $args =~ s/L[^;]*;/l/g;
- $args =~ tr/A-Z/a-z/;
- }
- $args =~ s/\[/_/g;
-
- $method =~ s/<init>/new/;
-
- print "\tjmethodID $name"."_$method"."_$args;\n";
- print "#define $name"."_$method"."_$args ${java_name}.$name"."_$method"."_$args\n" if $doJ;
- } elsif ($func =~ m/(.*),(.+),(.+)/) {
- my $field = $2;
- my $type = $3;
-
- print "\tjfieldID $name"."_$field;\n";
- print "#define $name"."_$field ${java_name}.$name"."_$field\n" if $doJ;
- } else {
- die("can't parse java signature $func");
- }
- }
- print "} ${java_name};\n";
- print "static const char *${java_name}_names =\n";
- foreach $func (@classes) {
- if ($func =~ m/^#(.+):(.+)/) {
- $name = $1;
- $class = $2;
-
- print "\t\"#$class\\0\"\n";
- } elsif ($func =~ m/static,(.*),(.*\(.*\).*)/) {
- print "\t\":$1\\0$2\\0\"\n";
- } elsif ($func =~ m/,(.*),(.*\(.*\).*)/) {
- print "\t\".$1\\0$2\\0\"\n";
- } elsif ($func =~ m/static,(.*),(.*)/) {
- print "\t\";$1\\0$2\\0\"\n";
- } elsif ($func =~ m/,(.*),(.*)/) {
- print "\t\",$1\\0$2\\0\"\n";
- }
- }
- print "\t;\n";
-}
+++ /dev/null
-/*
- * Copyright (C) 2017,2019 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * (1) Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * (2) Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * (3)The name of the author may not be used to
- * endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-
-#include "nativez.h"
-#include "nativez-jni.h"
-
-static JavaVM *vm;
-
-static jobject ByteOrder_nativeOrder; // ByteOrder.nativeOrder()
-
-static int fail(const char *ctx, const char *what) __attribute__ ((noinline));
-static int fail(const char *ctx, const char *what) {
- fprintf(stderr, "%s: %s\n", ctx, what);
- perror(ctx);
- fflush(stderr);
- return -1;
-}
-
-jint nativez_OnLoad(JavaVM *vmi, JNIEnv *env) {
- /* Save VM - required for callbacks from threads */
- vm = vmi;
-
- if (nativez_ResolveReferences(env, java_names, &java) != 0)
- return -1;
-
- jclass jc = (*env)->FindClass(env, "java/nio/ByteOrder");
- if (jc == NULL)
- return -1;
- jmethodID ByteOrder_nativeOrder_ = (*env)->GetStaticMethodID(env, jc, "nativeOrder", "()Ljava/nio/ByteOrder;");
- ByteOrder_nativeOrder = (*env)->NewGlobalRef(env, (*env)->CallStaticObjectMethodA(env, jc, ByteOrder_nativeOrder_, NULL));
-
- return 0;
-}
-
-void *nativez_AllocAligned(size_t align, size_t size) {
- void *mem;
-#ifdef HAVE_ALIGNED_MALLOC
- mem = _aligned_malloc(size, align);
- return mem;
-#else
- if (posix_memalign(&mem, align, size) == 0)
- return mem;
- return NULL;
-#endif
-}
-
-void nativez_FreeAligned(void *mem) {
-#ifdef HAVE_ALIGNED_MALLOC
- _aligned_free(mem);
-#else
- free(mem);
-#endif
-}
-
-JNIEnv *nativez_AttachCurrentThread(void) {
- JNIEnv *env = NULL;
-
- (*vm)->AttachCurrentThread(vm, (void **)&env, NULL);
-
- // one assumes this is null if error return?
- return env;
-}
-
-JNIEnv *nativez_AttachCurrentThreadAsDaemon(void) {
- JNIEnv *env = NULL;
-
- (*vm)->AttachCurrentThreadAsDaemon(vm, (void **)&env, NULL);
-
- // one assumes this is null if error return?
- return env;
-}
-
-/* ********************************************************************** */
-
-void nativez_ThrowException(JNIEnv *env, const char *type, const char *msg) {
- jclass jc = (*env)->FindClass(env, type);
-
- if (jc)
- (*env)->ThrowNew(env, jc, msg);
- else {
- fprintf(stderr, "Unable to throw exception `%s': `%s'\n", type, msg);
- fflush(stderr);
- }
-}
-
-void nativez_ThrowOutOfMemoryError(JNIEnv *env, const char *msg) {
- nativez_ThrowException(env, "java/lang/OutOfMemoryError", msg);
-}
-
-/* ********************************************************************** */
-
-jobject nativez_NewDirectBuffer(JNIEnv *env, void *data, jlong size) {
- jobject jo;
-
- jo = (*env)->NewDirectByteBuffer(env, data, size);
- if (jo) {
- jvalue arg = { .l = ByteOrder_nativeOrder };
-
- (*env)->CallObjectMethodA(env, jo, ByteBuffer_order_l, &arg);
- }
-
- return jo;
-}
-
-jboolean nativez_BufferHasArray(JNIEnv *env, jobject jbuffer) {
- return (*env)->CallBooleanMethodA(env, jbuffer, Buffer_hasArray_, NULL);
-}
-
-jboolean nativez_BufferIsDirect(JNIEnv *env, jobject jbuffer) {
- return (*env)->CallBooleanMethodA(env, jbuffer, Buffer_isDirect_, NULL);
-}
-
-jarray nativez_BufferArray(JNIEnv *env, jobject jbuffer) {
- return (*env)->CallObjectMethodA(env, jbuffer, Buffer_array_, NULL);
-}
-
-jint nativez_BufferArrayOffset(JNIEnv *env, jobject jbuffer) {
- return (*env)->CallIntMethodA(env, jbuffer, Buffer_arrayOffset_, NULL);
-}
-
-jint nativez_BufferPosition(JNIEnv *env, jobject jbuffer) {
- return (*env)->CallIntMethodA(env, jbuffer, Buffer_position_, NULL);
-}
-
-jint nativez_BufferLimit(JNIEnv *env, jobject jbuffer) {
- return (*env)->CallIntMethodA(env, jbuffer, Buffer_limit_, NULL);
-}
-
-void nativez_BufferSetPosition(JNIEnv *env, jobject jbuffer, jint jposition) {
- jvalue arg = { .i = jposition };
- (void)(*env)->CallObjectMethodA(env, jbuffer, Buffer_position_i, &arg);
-}
-
-void nativez_BufferSetLimit(JNIEnv *env, jobject jbuffer, jint jlimit) {
- jvalue arg = { .i = jlimit };
- (void)(*env)->CallObjectMethodA(env, jbuffer, Buffer_limit_i, &arg);
-}
-
-const char *nativez_GetString(JNIEnv *env, jstring js) {
- return js ? (*env)->GetStringUTFChars(env, js, NULL) : NULL;
-}
-
-void nativez_ReleaseString(JNIEnv *env, jstring js, const char *s) {
- if (js)
- (*env)->ReleaseStringUTFChars(env, js, s);
-}
-
-jstring nativez_NewString(JNIEnv *env, const char *s) {
- return s ? (*env)->NewStringUTF(env, s) : NULL;
-}
-
-/* ********************************************************************** */
-
-int nativez_ResolveReferences(JNIEnv *env, const char *jn_names, void *jnp) {
- jclass jc = NULL;
-
- const char *name = jn_names;
- int index = 0;
- void **jn = jnp;
- const char *cname = "?";
-
- while (*name) {
- const char *next = name + strlen(name) + 1;
-
- switch (*name) {
- case '#': // class
- jc = (*env)->FindClass(env, name + 1);
- jn[index] = jc = (*env)->NewGlobalRef(env, jc);
- cname = name + 1;
- break;
- case ',': // field
- jn[index] = (*env)->GetFieldID(env, jc, name+1, next);
- break;
- case ';': // static field
- jn[index] = (*env)->GetStaticFieldID(env, jc, name+1, next);
- break;
- case '.': // method
- jn[index] = (*env)->GetMethodID(env, jc, name+1, next);
- break;
- case ':': // static method
- jn[index] = (*env)->GetStaticMethodID(env, jc, name+1, next);
- break;
- default:
- return fail("Invalid table", name);
- }
-
- if (!jn[index])
- return fail(cname, name+1);
-
- if (*name != '#')
- next = next + strlen(next) + 1;
- name = next;
- index += 1;
- }
-
- return 0;
-}
-
-/* ********************************************************************** */
-
-int nativez_NonNull(JNIEnv *env, const char *what, void *ptr) {
- if (ptr)
- return 1;
-
- nativez_ThrowException(env, "java/lang/NullPointerException", what);
- return 0;
-}
-
-/* ********************************************************************** */
-
-/**
- * Retrieve pointer arguments.
- *
- * MUST always call ReleasePointers() with the same arguments regardles of return value.
- */
-jboolean nativez_GetPointers(JNIEnv *env, const char *desc, nzpointer * __restrict info) {
- char c;
- while ((c = *desc++)) {
- switch (c) {
- case 'P':
- if (!info->object)
- goto nullPointer;
- case 'p':
- if (info->object && !(info->p.v = (*env)->GetPrimitiveArrayCritical(env, info->object, NULL)))
- return 0;
- break;
- case 'U':
- if (!info->object)
- goto nullPointer;
- case 'u':
- if (info->object && !(info->p.U = (*env)->GetStringUTFChars(env, info->object, NULL)))
- return 0;
- break;
- case 'N':
- if (!info->object)
- goto nullPointer;
- case 'n':
- if (info->object)
- info->p.N = (void *)(uintptr_t)(*env)->GetLongField(env, info->object, NativeZ_p);
- break;
- default:
- return 0;
- }
- info++;
- }
- return 1;
-
- nullPointer:
- nativez_ThrowException(env, "java/lang/NullPointerException", "Argument missing");
- return 0;
-}
-
-/**
- * Release pointer arguments.
- */
-void nativez_ReleasePointers(JNIEnv *env, const char *desc, nzpointer * __restrict info) {
- char c;
- while ((c = *desc++)) {
- if (info->p.v) {
- switch (c) {
- case 'P':
- case 'p':
- (*env)->ReleasePrimitiveArrayCritical(env, info->object, info->p.v, 0);
- break;
- case 'U':
- case 'u':
- (*env)->ReleaseStringUTFChars(env, info->object, info->p.U);
- break;
- case 'N':
- case 'n':
- break;
- }
- }
- info++;
- }
-}
-
-/* ********************************************************************** */
-
-jobject NativeZ_create(JNIEnv *env, jclass jc, void *p) {
- if (p) {
- jvalue jargs[] = {
- { .l = jc },
- { .j = (uintptr_t)p }
- };
-
- return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_create_lj, jargs);
- } else
- return NULL;
-}
-
-jobject NativeZ_register(JNIEnv *env, jobject jo) {
- if (jo) {
- jvalue jargs[] = {
- { .l = jo }
- };
-
- return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_register_l, jargs);
- } else
- return NULL;
-}
-
-jobject NativeZ_resolve(JNIEnv *env, jclass jc, void *p) {
- if (p) {
- jvalue jargs[] = {
- { .l = jc },
- { .j = (uintptr_t)p }
- };
-
- return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_resolve_lj, jargs);
- } else
- return NULL;
-}
-
-jobject NativeZ_refer(JNIEnv *env, jclass jc, void *p) {
- if (p) {
- jvalue jargs[] = {
- { .l = jc },
- { .j = (uintptr_t)p }
- };
-
- return (*env)->CallStaticObjectMethodA(env, NativeZ_classid, NativeZ_refer_lj, jargs);
- } else
- return NULL;
-}
-
-void *NativeZ_getP(JNIEnv *env, jobject jo) {
- if (jo)
- return (void *)(uintptr_t)(*env)->GetLongField(env, jo, NativeZ_p);
- else
- return NULL;
-}
+++ /dev/null
-java Buffer java/nio/Buffer {
- hasArray, ()Z
- isDirect, ()Z
- array, ()Ljava/lang/Object;
- arrayOffset, ()I
- position, ()I
- position, (I)Ljava/nio/Buffer;
- limit, ()I
- limit, (I)Ljava/nio/Buffer;
-}
-
-java ByteBuffer java/nio/ByteBuffer {
- order, (Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;
-}
-
-java NativeZ au/notzed/nativez/NativeZ {
- static create, (Ljava/lang/Class;J)Lau/notzed/nativez/NativeZ;
- static register, (Lau/notzed/nativez/NativeZ;)Lau/notzed/nativez/NativeZ;
- static resolve, (Ljava/lang/Class;J)Lau/notzed/nativez/NativeZ;
- static refer, (Ljava/lang/Class;J)Lau/notzed/nativez/NativeZ;
- p, J
-}
+++ /dev/null
-/*
- * Copyright (C) 2019 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * (1) Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * (2) Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * (3)The name of the author may not be used to
- * endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#include <string.h>
-#include <stdio.h>
-#include <dlfcn.h>
-
-#include "nativez.h"
-
-static int fail(const char *ctx, const char *what) {
- fprintf(stderr, "%s: %s\n", ctx, what);
- perror(ctx);
- fflush(stderr);
- return -1;
-}
-
-int nativez_ResolveLibraries(JNIEnv *env, NZLibTable *table) {
- for (int i=0;table[i].name;i++) {
- table[i].lib = dlopen(table[i].path, RTLD_LAZY | RTLD_GLOBAL);
- if (!table[i].lib && (table[i].flags & NZSO_NONCORE) == 0)
- return fail("open library", table[i].path);
- }
- return 0;
-}
-
-int nativez_ResolveFunctions(JNIEnv *env, const NZLibTable *table, const char *fn_names, void *fnp) {
- void *lib = NULL;
- const char *name = fn_names;
- int index = 0;
- const char *lib_name = "";
- void **fn = fnp;
- int lib_flags = 0;
-
- while (*name) {
- const char *next = name + strlen(name) + 1;
-
- if (*name == '#') {
- lib = NULL;
- lib_flags = 0;
- lib_name = name+1;
- for (int i=0;table[i].name;i++) {
- if (strcmp(table[i].name, lib_name) == 0) {
- lib = table[i].lib;
- lib_flags = table[i].flags;
- break;
- }
- }
- } else if (lib) {
- void *entry = dlsym(lib, name);
-
- fn[index++] = entry;
- if (!entry)
- return fail("resolve function", name);
- } else if ((lib_flags & NZSO_NONCORE) == 0) {
- return fail(lib_name, name);
- }
-
- name = next;
- }
-
- return 0;
-}
+++ /dev/null
-/*
- * Copyright (C) 2019 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * (1) Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * (2) Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * (3)The name of the author may not be used to
- * endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#include <windows.h>
-
-#include "nativez.h"
-
-static int fail(const char *ctx, const char *what) {
- fprintf(stderr, "%s: %s\n", ctx, what);
- perror(ctx);
- fflush(stderr);
- return -1;
-}
-
-int nativez_ResolveLibraries(JNIEnv *env, NZLibTable *table) {
- for (int i=0;table[i].name;i++) {
- table[i].lib = LoadLibrary(table[i].path);
- if (!table[i].lib && (table[i].flags & NZSO_NONCORE) == 0) {
- return fail("open library", table[i].path);
- }
- }
- return 0;
-}
-
-int nativez_ResolveFunctions(JNIEnv *env, const NZLibTable *table, const char *fn_names, void *fnp) {
- void *lib = NULL;
- const char *name = fn_names;
- int index = 0;
- const char *lib_name = "";
- void **fn = fnp;
- int lib_flags = 0;
-
- while (*name) {
- const char *next = name + strlen(name) + 1;
-
- if (*name == '#') {
- lib = NULL;
- lib_flags = 0;
- lib_name = name+1;
- for (int i=0;table[i].name;i++) {
- if (strcmp(table[i].name, lib_name) == 0) {
- lib = table[i].lib;
- lib_flags = table[i].flags;
- break;
- }
- }
- } else if (lib) {
- void *entry = GetProcAddress(lib, name);
-
- fn[index++] = entry;
- if (!entry)
- return fail("resolve function", name);
- } else if ((lib_flags & NZSO_NONCORE) == 0) {
- return fail(lib_name, name);
- }
-
- name = next;
- }
-
- return 0;
-}
+++ /dev/null
-/*
- * Copyright (C) 2018 Michael Zucchi
- *
- * This file is part of nativez <https://www.zedzone.space/software/nativez.html>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * (1) Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * (2) Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * (3)The name of the author may not be used to
- * endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#ifndef _NATIVEZ_H
-#define _NATIVEZ_H
-
-#include <jni.h>
-
-/**
- * Resolves a java table created by nativez-gen
- */
-int nativez_ResolveReferences(JNIEnv *env, const char *jn_names, void *jnp);
-
-/**
- * Support for dynamic library loading.
- *
- * It happens in two parts:
- *
- * 1. load the libraries using nativez_ResolveLibraries;
- * 2. resolve the function pointers using nativez_ResolveFunctions.
- *
- */
-typedef struct NZLibTable {
- const char *name; /* portable name of library */
- const char *path; /* platform-specific name of library file */
- const int flags; /* NZSO_* flags */
- void *lib; /* pointer to library handle */
-} NZLibTable;
-
-// Define NZLibTable entry.
-#define NZSO(name, path, flags) { name, path, flags }
-#define NZSO_END { 0 }
-
-// non-core library, non-existance is not a failure
-#define NZSO_NONCORE 1
-
-/**
- * ResolveLibraries loads libraires via a table of NZLibTable entries.
- * The library name is platform specific. Also see NZSO_* flags.
- * The opened library is stored in the table[].lib.
- * The table is terminated with an empty entry or NZSO_END.
- */
-int nativez_ResolveLibraries(JNIEnv *env, NZLibTable *table);
-
-/**
- * ResolveFunctions uses the output of nativez-gen to fill out
- * a table.
- */
-int nativez_ResolveFunctions(JNIEnv *env, const NZLibTable *table, const char *fn_names, void *fnp);
-
-// Check if function exists, return value if not and set an exception
-#define NZCHECK_RET(env, x, ret) do { if (!nativez_NonNull(env, #x, (void *)fn.x)) { return ret; } } while (0)
-// Call a function pointer, or just use the struct directly.
-#define NZCALL(x) (fn.x)
-
-/**
- * Checks if ptr is non-null. If it is null throws a NullPointer
- * exception with the given description and returns false.
- */
-int nativez_NonNull(JNIEnv *env, const char *what, void *ptr);
-
-/**
- * For converting array or string arguments to native pointers.
- */
-typedef struct nzpointer nzpointer;
-
-struct nzpointer {
- const jobject object;
- union {
- void *v;
- jint *i;
- jfloat *f;
- jdouble *d;
- const char *U;
- void *N;
- } p;
-};
-
-/**
- * Retrieve pointers from java objects.
- *
- * desc is a string which defines the types of the arguments.
- *
- * p j*Array as GetPrimitiveArrayCritical
- * u String as UTF8
- * n Native.Z p pointer
- * P j*Array that may not be null
- * U String that may not be null
- * N NativeZ that may not be null
- *
- * info is an array of initialsed nzpointer objects with the .object field set to the
- * source object and the p field nullified. Each element corresponds to the
- * same index desc.
- *
- * Note: MUST always call ReleasePointers() with the same arguments regardles of return value.
- *
- * Returns true if all pointers were resolved successfully.
- */
-jboolean nativez_GetPointers(JNIEnv *env, const char *desc, nzpointer * __restrict info);
-void nativez_ReleasePointers(JNIEnv *env, const char *desc, nzpointer *info);
-
-/***
- * Call GetStringUTFChars but handles null js
- */
-const char *nativez_GetString(JNIEnv *env, jstring js);
-
-/**
- * Call ReleaseStringUTFChars but handles null js
- */
-void nativez_ReleaseString(JNIEnv *env, jstring js, const char *s);
-
-/**
- * Call NewStringUTF but handle NULL s
- */
-jstring nativez_NewString(JNIEnv *env, const char *s);
-
-/**
- * Throw a basic named exception with a simple message.
- */
-void nativez_ThrowException(JNIEnv *env, const char *cname, const char *msg);
-void nativez_ThrowOutOfMemoryError(JNIEnv *env, const char *msg);
-
-/**
- * (Byte)Buffer methods
- */
-jobject nativez_NewDirectBuffer(JNIEnv *env, void *data, jlong size);
-jboolean nativez_BufferHasArray(JNIEnv *env, jobject jbuffer);
-jboolean nativez_BufferIsDirect(JNIEnv *env, jobject jbuffer);
-jarray nativez_BufferArray(JNIEnv *env, jobject jbuffer);
-jint nativez_BufferArrayOffset(JNIEnv *env, jobject jbuffer);
-jint nativez_BufferPosition(JNIEnv *env, jobject jbuffer);
-jint nativez_BufferLimit(JNIEnv *env, jobject jbuffer);
-void nativez_BufferSetPosition(JNIEnv *env, jobject jbuffer, jint jposition);
-void nativez_BufferSetLimit(JNIEnv *env, jobject jbuffer, jint jlimit);
-
-/**
- * Allocate aligned memory, throws an exception on error.
- */
-void *nativez_AllocAligned(size_t align, size_t size);
-void nativez_FreeAligned(void *mem);
-
-/*
- Call this in the OnLoad method
-*/
-jint nativez_OnLoad(JavaVM *vmi, JNIEnv *env);
-
-/*
- Attach current thread, so it can be used in callbacks.
- */
-JNIEnv *nativez_AttachCurrentThread(void);
-JNIEnv *nativez_AttachCurrentThreadAsDaemon(void);
-
-/*
- It is always safe to call resolve() for the freeable object case,
- create() just implements an optimised path.
-
- resolve() is required for objects which are refcounted on the C side.
- */
-
-/* Create a new object, e.g. one that has just been allocated and can't possibly already exist in java */
-jobject NativeZ_create(JNIEnv *env, jclass jc, void *p);
-/* Register a newly created object */
-jobject NativeZ_register(JNIEnv *env, jobject object);
-/* Turn a pointer into a freeable object. If it already exists the same object will be returned */
-jobject NativeZ_resolve(JNIEnv *env, jclass jc, void *p);
-/* Turn a pointer into a non-freeable object. If it already exists the same object will be returned */
-jobject NativeZ_refer(JNIEnv *env, jclass jc, void *p);
-/* Retreive the native pointer stored in NativeZ subclass instance jo */
-void *NativeZ_getP(JNIEnv *env, jobject jo);
-
-#endif
--- /dev/null
+
+# TODO: define more & consistent stage processing hooks, currently implicit require init() and stage.postprocess
+# TODO: code:<inline> etc should probably instead be code:<type> where <type> is one of the template fields - methods, init, etc.
+# TODO: calls should probably use dynamic invocation - downcall is not bound to address
+
+package api;
+
+use strict;
+
+use File::Path qw(make_path);
+use File::Basename;
+use Data::Dumper;
+use List::Util qw(first);
+use Carp 'verbose';
+
+use config;
+
+my %renameTable = (
+ 'studly-caps' => sub { my $s = shift; $s =~ s/(?:^|_)(.)/\U$1/g; return $s; },
+ 'camel-case' => sub { my $s = shift; $s =~ s/(?:_)(.)/\U$1/g; return $s; },
+ 'upper-leadin' => sub { my $s = shift; $s =~ s/^([^_]+)/\U$1/; return $s; },
+ 'identity' => sub { return $_[0]; },
+ 'call' => sub {
+ my $s = shift;
+
+ if ($s =~ m/\(/) {
+ $s =~ s/u32:|u64:/p/g;
+ $s =~ s/\$\{([^\}]+)\}/$1/g;
+ $s =~ s/[\(\)]/_/g;
+ $s =~ s/^/Call/;
+ }
+ $s;
+ }
+);
+
+my %defaultTable = (
+ 'struct:<default>' => {
+ name => '<default>',
+ items => [],
+ options => [ 'default=none', 'access=rw', 'field:rename=studly-caps' ],
+ regex => qr/^struct:<default>$/,
+ type => 'struct'
+ },
+ 'union:<default>' => {
+ name => '<default>',
+ items => [],
+ options => [ 'default=all', 'access=rw', 'field:rename=studly-caps' ],
+ regex => qr/^union:<default>$/,
+ type => 'union'
+ },
+ 'call:<default>' => {
+ name => '<default>',
+ items => [],
+ options => [ 'call:rename=call', 'access=r' ],
+ regex => qr/^call:<default>$/,
+ type => 'call'
+ },
+ 'func:<default>' => {
+ name => '<default>',
+ items => [],
+ options => [],
+ regex => qr/^func:<default>$/,
+ type => 'func',
+ 'func:template' => 'code:method=invoke'
+ },
+ 'enum:<default>' => {
+ name => '<default>',
+ items => [],
+ options => [],
+ regex => qr/^enum:<default>$/,
+ type => 'enum'
+ },
+);
+
+sub new {
+ my $class = shift;
+ my $file = shift;
+ my $vars = shift;
+ my $self = {
+ vars => $vars,
+ index => {},
+ data => {},
+ types => [],
+ };
+
+ my $conf = new config($vars, $file);
+ $self->{api} = $conf->{objects};
+ foreach my $obj (@{$self->{api}}) {
+ $self->{index}->{"$obj->{type}:$obj->{name}"} = $obj;
+ }
+ foreach my $k (keys %defaultTable) {
+ if (!defined($self->{index}->{$k})) {
+ $self->{index}->{$k} = $defaultTable{$k};
+ push @{$self->{api}}, $defaultTable{$k};
+ }
+ }
+
+ while ($#_ >= 0) {
+ my $name = shift;
+ my $info = loadAPIFile($name);
+
+ $self->{data} = { %{$self->{data}}, %{$info} };
+ }
+
+ # FIXME: fix the list names in export.cc and export-defines instead
+ # set all the member list names to be consistent
+ foreach my $s (values %{$self->{data}}) {
+ foreach my $n ('fields', 'arguments', 'values') {
+ if ($s->{$n}) {
+ $s->{items} = $s->{$n};
+ delete $s->{$n};
+ }
+ }
+ }
+
+ bless $self, $class;
+
+ # handle requires and init modules
+ foreach my $p (@{$conf->{pragmas}}) {
+ if ($p->[0] eq '%require') {
+ require $p->[1];
+ my $n = $p->[1];
+ $n =~ s/.pm$/::init(\$self)/;
+ print "$n\n";
+ eval $n || die "module init failed: $! $@";
+ }
+ }
+
+ # create type indices
+ {
+ my $index = {};
+ map { $index->{$_->{type}}->{$_->{name}} = $_ } values %{$self->{data}};
+ $self->{databytype} = $index;
+ }
+
+ analyseAPI($self);
+ preprocess($self);
+
+ # add phantom 'api' entries for anything else required
+ foreach my $s (findDependencies($self)) {
+ my $n = "$s->{type}:$s->{name}";
+ my $def = $self->{index}->{"$s->{type}:<default>"};
+
+ die "no default for implicit dependency $n" if (!$def);
+
+ my $obj = {
+ %$def,
+ match => "$s->{type}:$s->{name}",
+ name => $s->{name},
+ regex => qr/^$n$/,
+ };
+ $obj->{rename} = $obj->{"$obj->{type}:rename"} ? $obj->{"$obj->{type}:rename"}->($obj->{name}) : $obj->{name};
+
+ print " implicit $n\n";
+ push @{$self->{api}}, $obj;
+ $self->{index}->{$n} = $obj;
+ }
+
+ postprocess($self);
+
+ foreach my $p (@{$conf->{pragmas}}) {
+ if ($p->[0] eq '%stage.postprocess') {
+ eval "$p->[1](\$self)" || die "$p->[0] $p->[1] failed: $! $@";
+ }
+ }
+
+ # form the types
+ # TODO: could match the regexes to every possible type in the api and create a direct index
+ my $copyIndex = {};
+ foreach my $obj (grep { $_->{type} eq 'type' && $_->{name} =~ m@<.*>@ } @{$self->{api}}) {
+ $copyIndex->{$obj->{name}} = $obj;
+ }
+
+ foreach my $obj (grep { $_->{type} eq 'type' && $_->{name} =~ m@^/.*/$@ } @{$self->{api}}) {
+ push @{$self->{types}}, initType($self, $copyIndex, $obj);
+ }
+
+ # handle external references - rename and supress output
+ # Not sure of the best place to put this
+ foreach my $obj (grep { $_->{type} eq 'extern' } @{$self->{api}}) {
+ foreach my $inc (@{$obj->{items}}) {
+ my @list = findMatches($self, $inc, $obj);
+
+ foreach my $s (@list) {
+ $s->{rename} = $obj->{name}.'.'.$inc->{"$s->{type}:rename"}->($s->{name}, $s);
+ $s->{output} = 0;
+ }
+ }
+ }
+
+ return $self;
+}
+
+# class.name to class/name.java
+sub classToPath {
+ my $api = shift;
+ my $name = shift;
+
+ $name = $api->{vars}->{package}.'.'.$name;
+ $name =~ s@\.@/@g;
+ $name = $api->{vars}->{output}.'/'.$name.'.java';
+ $name;
+}
+
+sub closeOutput {
+ my $api = shift;
+ my $name = shift;
+ my $f = shift;
+ my $path = $api->classToPath($name);
+
+ close($f) || die;
+ rename($path.'~', $path) || die ("rename failed: $!");
+}
+
+sub openOutput {
+ my $api = shift;
+ my $name = shift;
+ my $path = $api->classToPath($name);
+ my $dir = dirname($path);
+
+ make_path($dir) if (!-d $dir);
+
+ open(my $f, ">", $path.'~') || die ("Cannot open '$path' for writing");
+ #print "writing '$path'\n";
+ $f;
+}
+
+sub renameFunction {
+ my $api = shift;
+ my $name = shift;
+ $renameTable{$name};
+}
+
+sub initType {
+ my $api = shift;
+ my $index = shift;
+ my $obj = shift;
+ my $type = {};
+
+ $type->{options} = [ @{$obj->{options}} ];
+ $type->{file} = $obj->{file};
+
+ # FIXME: per-item options, set etc?
+ foreach my $inc (grep { defined $_->{literal} } @{$obj->{items}}) {
+ $type->{items}->{$inc->{match}} = $inc->{literal};
+ }
+
+ my $v = optionValue('select', undef, $obj);
+ $type->{select} = $v if defined($v);
+
+ #print "init $obj->{name}\n";
+ foreach my $c (split /,/,optionValue('copy', undef, $obj)) {
+ my $copy = $index->{$c};
+
+ if ($copy) {
+ #print " copy $c\n";
+ my $proto = initType($api, $index, $copy);
+
+ foreach my $k (keys %{$proto->{items}}) {
+ $type->{items}->{$k} = $proto->{items}->{$k} if !defined($type->{items}->{$k});
+ }
+
+ push @{$type->{options}}, @{$proto->{options}};
+ } else {
+ die ("type copy target $c not found");
+ }
+ }
+
+ $type->{regex} = qr/$1/ if $obj->{name} =~ m@/(.*)/@;
+
+ return $type;
+}
+
+# returns { type=>$type, match=>$match)
+# match is named groups from regex (%+)
+sub findType {
+ my $api = shift;
+ my $m = shift;
+ my $deref = $m->{deref};
+ my @list;
+
+ foreach my $type (@{$api->{types}}) {
+ my $select = !defined($type->{select}) || defined($m->{select}->{$type->{select}});
+
+ if ($select && ($deref =~ m/$type->{regex}/)) {
+ my %match = %+;
+
+ return { deref=>$deref, type=>$type, match=>\%match };
+ }
+ }
+ die ("cannot find matching type '$deref' for member $m->{name}");
+ undef;
+}
+
+sub findTemplateName {
+ my $api = shift;
+ my $name = shift;
+
+ if ($name =~ m/^(.+)=(.+)$/) {
+ my $template = findItem($api->{index}->{$1}, $2);
+ return $template if defined $template;
+ }
+ die "can't find template '$name'\n";
+}
+
+sub queryTemplate {
+ my $api = shift;
+ my $obj = shift;
+ my $s = shift;
+ my $sname = $s->{"$s->{type}:template"};
+ my $def = $api->{index}->{"$s->{type}:<default>"};
+ my $name = $sname ? $sname : api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def);
+
+ return $name;
+}
+
+sub findTemplate {
+ my $api = shift;
+ my $obj = shift;
+ my $s = shift;
+ my $sname = $s->{"$s->{type}:template"};
+ my $def = $api->{index}->{"$s->{type}:<default>"};
+ my $name = $sname ? $sname : api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def);
+
+ print "template: $s->{name} -> $name\n" if ($api->{vars}->{verbose} > 1);
+
+ return findTemplateName($api, $name);
+}
+
+# find value of first option of the given name
+# TBD: moved to config.pm
+sub optionValue {
+ my $name = shift;
+ my $or = shift;
+
+ #my $match = sub { $_ =~ m/^\Q$name\E=(.*)$/on ? $1 : undef };
+
+ #print "optionValue $name\n";
+ foreach my $obj (@_) {
+ foreach my $opt (@{$obj->{options}}) {
+ #print "? $name $opt = ".($opt =~m/^\Q$name\E=(.*)$/)."\n";
+ #print " = $opt\n" if ($opt =~m/^\Q$name\E=(.*)$/);
+ return $1 if ($opt =~m/^\Q$name\E=(.*)$/);
+ #my $x = $match->($opt);
+ #return $x if defined($x);
+ }
+ }
+ $or;
+}
+
+# look for all matching options of the given name
+# multiple objects are searched, the first one with
+# the given parameter overrides the rest.
+# name, object, object *
+sub optionValues {
+ my $name = shift;
+ my $rx = qr/$name/;
+ my @list;
+
+ foreach my $obj (@_) {
+ foreach my $opt (@{$obj->{options}}) {
+ push @list, $1 if ($opt =~m/^$rx=(.*)$/);
+ }
+ last if ($#list >= 0);
+ }
+ @list;
+}
+
+# same as above but doesn't short-circuit
+sub optionValuesAll {
+ my $name = shift;
+ my $rx = qr/$name/;
+ my @list;
+
+ foreach my $obj (@_) {
+ foreach my $opt (@{$obj->{options}}) {
+ push @list, $1 if ($opt =~m/^$rx=(.*)$/);
+ }
+ }
+ @list;
+}
+
+# find first occurance of a flag
+# TBD: moved to config.pm
+sub optionFlag {
+ my $name = shift;
+
+ foreach my $obj (@_) {
+ foreach my $opt (@{$obj->{options}}) {
+ return 1 if ($opt eq $name);
+ }
+ }
+ undef;
+}
+
+sub findItem {
+ my $s = shift;
+ my $name = shift;
+
+ foreach my $i (@{$s->{items}}) {
+ return $i if $i->{match} eq $name;
+ }
+ undef;
+}
+
+sub findAllItems {
+ my $api = shift;
+ my $obj = shift;
+ my $s = shift;
+ my %visited = ();
+ my @fields = ();
+
+ #print Dumper($obj);
+
+ my @all = @{$s->{items}};
+ my %index;
+
+ foreach my $m (@all) {
+ $index{$m->{name}} = $m;
+ }
+
+ foreach my $inc (@{$obj->{items}}) {
+ my $d = $index{$inc->{match}};
+
+ if ($d) {
+ next if $visited{$d->{type}.':'.$d->{name}}++;
+ push @fields, [ $inc, $d ];
+ } else {
+ foreach my $d (grep { $_->{name} =~ m/$inc->{regex}/ } @all) {
+ next if $visited{$d->{type}.':'.$d->{name}}++;
+ push @fields, [ $inc, $d ];
+ }
+ }
+ }
+
+ if (optionValue('default', undef, $obj, $api->{index}->{'struct:<default>'}) eq 'all') {
+ #print "* add all items\n";
+ foreach my $d (@all) {
+ next if $visited{$d->{type}.':'.$d->{name}}++;
+ push @fields, [ $obj, $d ];
+ }
+ }
+
+ return @fields;
+}
+
+sub findField {
+ my $s = shift;
+ my $name = shift;
+
+ foreach my $i (@{$s->{items}}) {
+ return $i if $i->{name} eq $name;
+ }
+ undef;
+}
+
+# ######################################################################
+
+sub addDependencies {
+ my $api = shift;
+ my $obj = shift;
+ my $s = shift;
+ my $add = shift;
+
+ #print "add deps for '$s->{name}'\n";
+ if ($s->{type} =~ m/^(struct|union)$/n) {
+ # include embedded structures always
+ foreach my $d (grep { $_->{type} =~ m/^(struct|union):/ && $_->{deref} =~ m/\[\d+\$\{|^\$\{/ } @{$s->{items}}) {
+ #print " embedded $d->{name} $d->{deref}\n";
+ $add->($d->{type});
+ }
+
+ # include selected fields optionally
+ if ($obj) {
+ foreach my $i (findAllItems($api, $obj, $s)) {
+ my ($inc, $d) = @{$i};
+ #print " selected $d->{name} $d->{type} $d->{deref}\n";
+ $add->($d->{type}) if ($d->{type} =~ m/^(struct|union|func|call|enum):/);
+ # HACK: enum types are integers but ctype includes the actual type
+ $add->("enum:$1") if ($d->{ctype} =~ m/^enum (.+)/);
+ }
+ }
+ } elsif ($s->{type} =~ m/^(call|func)/n) {
+ # for calls/func need all fields
+ foreach my $d (grep { $_->{type} =~ m/^(struct|union|func|call|enum):/ } @{$s->{items}}, $s->{result}) {
+ #print " argument $d->{name} $d->{type} $d->{deref}\n";
+ $add->($d->{type});
+ }
+ }
+}
+
+# use either {match} or {regex} or {matcher} to get all matches for a data type
+sub findMatches {
+ my $api = shift;
+ my $inc = shift;
+ my $ctx = shift;
+ my $type = $inc->{type};
+ my $data = $api->{databytype}->{$type};
+
+ if ($inc->{matcher}) {
+ #grep { $inc->{matcher}->($_, $ctx) } grep { $_->{type} eq $inc->{type} } values %$data;
+ grep { $inc->{matcher}->($_, $ctx) } values %$data;
+ } else {
+ my $s = $api->{data}->{$inc->{match}};
+
+ if (defined($s)) {
+ $s;
+ } else {
+ #map { $data->{$_} } grep { $_ =~ m/$inc->{regex}/ } keys %$data;
+ #map { $data->{$_} } grep { "$type:$_" =~ m/$inc->{regex}/ } keys %$data;
+ grep { "$_->{type}:$_->{name}" =~ m/$inc->{regex}/ } values %$data;
+ #grep { "$_->{type}:$_->{name}" =~ m/$inc->{regex}/ } values %{$api->{data}};
+ }
+ }
+}
+
+# find all extra types used by the api requested
+sub findDependencies {
+ my $api = shift;
+ my %data = %{$api->{data}};
+ my %seen;
+ my %deps;
+ my $setdeps = sub { my $d = shift; $deps{$d} = 1; };
+
+ print "Root types\n";
+ foreach my $obj (@{$api->{api}}) {
+ if ($obj->{type} eq 'library') {
+ foreach my $inc (@{$obj->{items}}) {
+ next if ($inc->{type} eq 'library');
+
+ print "? $inc->{match}\n";
+ foreach my $s (findMatches($api, $inc, $obj)) {
+ my $n = "$s->{type}:$s->{name}";
+
+ print "+ $n\n";
+
+ $seen{$n}++;
+ $s->{output} = 1;
+ addDependencies($api, $obj, $s, $setdeps);
+ }
+ }
+ } elsif ($obj->{type} =~ m/^(struct|union|call|func|enum|define)$/) {
+ #foreach my $n (grep { $_ =~ m/$obj->{regex}/ } keys %data) {
+
+ print "? $obj->{name}\n";
+
+
+ foreach my $s (findMatches($api, $obj, $obj)) {
+ my $n = "$s->{type}:$s->{name}";
+
+ print "+ $n\n";
+
+ $seen{$n}++;
+ $s->{output} = 1;
+ addDependencies($api, $obj, $s, $setdeps);
+ }
+ }
+ }
+
+ # at this point 'seen' contains everything explicitly requested
+ # and deps is anything else they need but not referenced directly
+ # recursively grab anything else
+
+ my @list = ();
+ my @stack = sort keys %deps;
+ my $pushstack = sub { my $d = shift; push @stack, $d; };
+ while ($#stack >= 0) {
+ my $n = shift @stack;
+ my $s;
+
+ next if $seen{$n}++;
+
+ $s = $data{$n};
+
+ if ($s) {
+ print "Add referent: $n\n";
+ $s->{output} = 1;
+ addDependencies($api, $api->{index}->{"$s->{type}:<default>"}, $s, $pushstack);
+ } elsif ($n =~ m/^(.*):(.*)$/) {
+ print "Add anonymous: $n\n";
+ # type not know, add anonymous
+ $s = {
+ name => $2,
+ type => $1,
+ size => 0,
+ items => [],
+ output => 1,
+ };
+ $api->{data}->{$n} = $s;
+ }
+
+ # maybe it should have some skeleton metadata?
+ # depends on where it's used i suppose
+ push @list, $s;
+ }
+
+ print "Added ".($#list+1)." dependencies\n";
+ return @list;
+}
+
+# ######################################################################
+
+sub loadAPIFile {
+ my $file = shift;
+ my $info;
+
+ unless ($info = do $file) {
+ die "couldn't parse $file: $@" if $@;
+ die "couldn't import $file: $!" unless defined $info;
+ die "couldn't run $file" unless $info;
+ }
+
+ return $info;
+}
+
+sub parseRename {
+ my $api = shift;
+ my $how = shift;
+ my $rename = $renameTable{'identity'};
+
+ foreach my $n (split /,/,$how) {
+ my $old = $rename;
+ my $new = $renameTable{$n};
+
+ if ($n =~ m@^s/(.*)/(.*)/$@) {
+ my $rx = qr/$1/;
+ my $rp = $2;
+ $rename = sub { my $s=shift; $s = $old->($s); $s =~ s/$rx/$rp/; return $s;};
+ } elsif ($n =~ m@^func:(.*)$@) {
+ $rename = eval "sub { $1(\@_) }";
+ if (!defined($rename)) {
+ die "Unable to parse rename function '$1'";
+ }
+ } elsif ($new) {
+ $rename = sub { my $s=shift; $s = $old->($s); return $new->($s); };
+ } else {
+ my $x = $n;
+ $rename = sub { return $x; };
+ }
+ }
+ $rename;
+}
+
+# pre-process {data}
+sub preprocess {
+ my $api = shift;
+
+ # Find any anonymous types and add them in
+ my %anonymous = ();
+ foreach my $s (values %{$api->{data}}) {
+
+ foreach my $m (grep { $_->{type} =~ m/struct:|union:/} @{$s->{items}}) {
+ $anonymous{$m->{type}} = 1 if !defined($api->{data}->{$m->{type}});
+ }
+
+ # add 'result' name
+ $s->{result}->{name} = 'result$' if $s->{type} =~ m/func|call/;
+
+ # add a canonical deref for all types
+ if ($s->{type} =~ m/func|call|struct|union/) {
+ foreach my $m (grep { !defined($_->{deref}) } @{$s->{items}}, ($s->{type} =~ m/func|call/) ? $s->{result} : ()) {
+ if ($m->{type} =~ m/^(union|struct):(.*)/) {
+ $m->{deref} = "\${$2}";
+ } elsif ($m->{ctype} eq 'bitfield') {
+ $m->{deref} = "bitfield";
+ } else {
+ $m->{deref} = $m->{type};
+ }
+ }
+ }
+
+ # all 'defines' are output by default
+ $s->{output} = 1 if $s->{type} eq 'define';
+ }
+
+ foreach my $k (sort keys %anonymous) {
+ print " anon $k\n";
+ if ($k =~ m/^(.*):(.*)$/) {
+ $api->{data}->{$k} = {
+ name => $2,
+ type => $1,
+ size => 0,
+ items => [],
+ };
+ }
+ }
+}
+
+# preprocess {api}
+sub analyseAPI {
+ my $api = shift;
+
+ # Note that type:name regexes always start at the beginning
+
+ foreach my $obj (@{$api->{api}}) {
+ $obj->{match} = "$obj->{type}:$obj->{name}";
+
+ if ($obj->{name} =~ m@^/(.*)/$@) {
+ $obj->{regex} = qr/^$obj->{type}:$1/;
+ } elsif ($obj->{name} =~ m@^<invoke=(.*)>$@) {
+ $obj->{matcher} = eval "sub { $1(\@_) }";
+ die "unable to parse match function $obj->{name} $! $@" if !defined($obj->{matcher});
+ } else {
+ $obj->{regex} = qr/^$obj->{type}:$obj->{name}$/;
+ }
+
+ foreach my $opt (@{$obj->{options}}) {
+ if ($opt =~ m/^(.+:rename)=(.*)$/) {
+ $obj->{$1} = parseRename($api, $2);
+ } elsif ($opt =~ m/^rename=(.*)$/) {
+ $obj->{"$obj->{type}:rename"} = parseRename($api, $1);
+ }
+ }
+
+ my $defmode = ($obj->{type} eq 'library' ? 'func' : 'field');
+ foreach my $inc (@{$obj->{items}}) {
+ my $match = $inc->{match};
+ my $mode = $defmode;
+
+ if ($inc->{match} =~ m/^(.*?):(.*)$/) {
+ $match = $2;
+ $mode = $1;
+ }
+
+ $inc->{type} = $mode;
+ if ($match =~ m@^/(.*)/$@) {
+ $inc->{regex} = $mode ne 'field' ? qr/$mode:$1/ : qr/$1/;
+ } elsif ($match =~ m@^<invoke>$@) {
+ $inc->{matcher} = eval "sub { $inc->{literal} }";
+ die "unable to parse match function $inc->{literal} $! $@" if !defined($inc->{matcher});
+ } elsif ($match =~ m@^<invoke=(.*)>$@) {
+ $inc->{matcher} = eval "sub { $1(\@_) }";
+ die "unable to parse match function $inc->{match} $! $@" if !defined($inc->{matcher});
+ } else {
+ $inc->{regex} = $mode ne 'field' ? qr/^$mode:$match$/ : qr/^$match$/;
+ }
+
+ foreach my $opt (@{$inc->{options}}) {
+ if ($opt =~ m/^rename=(.*)$/) {
+ #print "option $opt ".Dumper($inc);
+ $inc->{"$mode:rename"} = parseRename($api, $1);
+ }
+ }
+
+ $inc->{"$mode:rename"} = $obj->{"$mode:rename"} if (!(defined($inc->{"$mode:rename"})) && defined($obj->{"$mode:rename"}));
+ }
+ }
+}
+
+#
+# 'lib/struct/func' level 'lib.inc' level 'func/struct.inc' level setting
+# array:name1 array:name1 array char* is array, void* is a Segment
+# array-size:name1=name2 array-size:name1=name2 array-size=name2 implied array size from name2
+# implied:name1=expr implied:name1=expr implied=expr value of name1 is calculated from other arguments
+# instance:name1 instance:name1 instance parameter/return is instance, implies non-static function
+
+# scope:name1=scope[,close] scope:name1=scope[,close] scope=scope[,close] parameter/return is a new instance with given scope
+# success:name1=values success:name1=values success=values which parameter and values indicate integer success
+# pointer types are considered in/out and implied
+# success:name1=!null success:name1=!null success=!null which parameter and values indicate non-null success
+# return:name1 return:name1 return return this output argument instead of the return code
+
+# access:name1=rwi access:name1=rwi access=rwi set access type, read, write, indexed
+# access=rwi access=rwi access=rwi - can also be set as default
+
+# onsuccess=blah onsuccess=blah onsuccess=blah
+
+# 'name' is parameter name, or parameter index, or result$ for return value
+# scope is instance for instance-scope, explicit for explicit scope, global for global scope. default is global.
+
+# err, maybe this format makes more sense:
+# name1:success=values name1:success=values success=values
+# name1:instance name1:instance instance
+
+my @itemFlags = (
+ 'array',
+ 'segment',
+ 'instance',
+ 'return',
+ 'raw',
+ 'raw-in',
+);
+
+my @itemOptions = (
+ 'array-size',
+ 'scope',
+ 'success',
+ 'implied',
+ 'tonative',
+);
+
+my @itemAnyOptions = (
+ 'access',
+);
+
+# process struct->field
+# api, obj (struct, func), inc (field entry), s (struct), m (struct field)
+# Note: keep in sync with processFunc
+sub processField {
+ my $api = shift;
+ my $obj = shift;
+ my $inc = shift;
+ my $s = shift;
+ my $m = shift;
+ my $def = $api->{index}->{"$s->{type}:<default>"};
+
+ #print "process $s->{type}:$s->{name}.$m->{name}\n";
+ print " $m->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+ foreach my $flag (@itemFlags) {
+ my $value = optionFlag("$flag", $inc);
+ $value = optionFlag("$flag:$m->{name}", $obj, $def) if !defined($value);
+ $m->{$flag} = $value if (defined($value));
+ $m->{select}->{$flag} = 1 if (defined($value));
+ }
+ foreach my $option (@itemOptions) {
+ my $value = optionValue("$option", undef, $inc);
+ $value = optionValue("$option:$m->{name}", undef, $obj, $def) if !defined($value);
+ $m->{$option} = $value if (defined($value));
+ $m->{select}->{$option} = 1 if (defined($value));
+ }
+ foreach my $option (@itemAnyOptions) {
+ my $value = optionValue("$option", undef, $inc);
+ $value = optionValue("$option:$m->{name}", undef, $obj, $def) if !defined($value);
+ $value = optionValue("$option", undef, $obj, $def) if !defined($value);
+ $m->{$option} = $value if (defined($value));
+ }
+
+ $m->{output} = 1;
+ $m->{rename} = (first { defined $_ } $inc->{'field:rename'}, $obj->{'field:rename'}, $def->{'field:rename'}, $renameTable{identity})->($m->{name}, $m, $s);
+}
+
+# process func or call main type
+sub processTypeFunc {
+ my $api = shift;
+ my $seen = shift;
+ my $obj = shift;
+ my $def = $api->{index}->{"$obj->{type}:<default>"};
+
+ foreach my $s (@_) {
+ my $v;
+
+ if ($seen->{"$s->{type}:$s->{name}"}++) { print "warning: seen $s->{type}:$s->{name}\n"; next; }
+
+ print " $s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+ foreach my $m ($s->{result}, @{$s->{items}}) {
+ my $inc = findItem($obj, $m->{name});
+
+ processField($api, $obj, $inc, $s, $m);
+ }
+
+ $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s);
+ $s->{access} = optionValue('access', '', $obj, $def);
+ $v = optionValue('onsuccess', undef, $obj, $def);
+ $s->{onsuccess} = $v if defined $v;
+
+ postProcessType($s);
+ }
+}
+
+# process library->func
+# process struct->func
+# api, obj (library, struct), inc (func entry), s ($data->{func:name})
+# Note: keep in sync with processField
+sub processFunc {
+ my $api = shift;
+ my $obj = shift;
+ my $inc = shift;
+ my $s = shift;
+ my @params = (defined($s->{result}) ? $s->{result} : (), @{$s->{items}});
+ my $index = -1;
+ my $lindex = -2 - $#params;
+ my $def = $api->{index}->{"$s->{type}:<default>"};
+ my $v;
+
+ print "process $s->{type}:$s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+ foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) {
+ #print "[$index] [$lindex] $m->{name}\n";
+
+ foreach my $flag (@itemFlags) {
+ my $value = optionFlag("$flag:$m->{name}", $inc, $obj, $def);
+ $value = optionFlag("$flag:$index", $inc, $obj, $def) if !defined($value);
+ $value = optionFlag("$flag:$lindex", $inc, $obj, $def) if !defined($value);
+ $m->{$flag} = $value if (defined($value));
+ $m->{select}->{$flag} = 1 if (defined($value));
+ }
+ foreach my $option (@itemOptions) {
+ my $value = optionValue("$option:$m->{name}", undef, $inc, $obj, $def);
+ $value = optionValue("$option:$index", undef, $inc, $obj, $def) if !defined($value);
+ $value = optionValue("$option:$lindex", undef, $inc, $obj, $def) if !defined($value);
+ $m->{$option} = $value if (defined($value));
+ $m->{select}->{$option} = 1 if (defined($value));
+ }
+ foreach my $option (@itemAnyOptions) {
+ my $value = optionValue("$option:$m->{name}", undef, $inc, $obj, $def);
+ $value = optionValue("$option:$index", undef, $inc, $obj, $def) if !defined($value);
+ $value = optionValue("$option:$lindex", undef, $inc, $obj, $def) if !defined($value);
+ $value = optionValue("$option", undef, $inc, $obj, $def) if !defined($value);
+ $m->{$option} = $value if (defined($value));
+ }
+ $m->{output} = 1;
+ $index++;
+ $lindex++;
+ }
+
+ $s->{rename} = (first { defined $_ } $inc->{"$s->{type}:rename"}, $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s);
+ $s->{access} = optionValue('access', '', $inc, $obj, $def);
+ $v = optionValue('onsuccess', undef, $inc, $obj, $def);
+ $s->{onsuccess} = $v if defined ($v);
+
+ postProcessType($s);
+}
+
+# finally link up array sizes and work out what is output or not
+# TODO: struct field array-size should be output but not included in constructors?
+sub postProcessType {
+ my $s = shift;
+ my $static = 1;
+
+ foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) {
+ $static = 0 if ($m->{instance});
+
+ # FIXME: collect multiple scopes here
+ $s->{scope} = 'explicit' if ($m->{scope} =~ m/^explicit$|^explicit,(.*)$/);
+ $s->{scope} = 'global' if ($m->{scope} eq 'global');
+ $s->{scope} = 'object' if ($m->{scope} eq 'object');
+
+ if ($m->{'array-size'}) {
+ my $size = findField($s, $m->{'array-size'});
+
+ print Dumper($s) if (!defined($size));
+ die "can't find array-size=$m->{'array-size'}" if !defined($size);
+
+ $size->{output} = 0 if ($s->{type} =~ m/func|call/);
+ $size->{'array-size-source'} = $m;
+ $m->{'array-size'} = $size;
+ }
+
+ # no java arg for instance parameter
+ $m->{output} = 0 if ($m->{instance});
+ # don't generate java args for values calculated
+ $m->{output} = 0 if (defined($m->{implied}));
+ # don't generate java args for return statuss unless they're also the constructed value
+ $m->{output} = 0 if (defined($m->{success}) && !$m->{scope});
+ # don't generate java args for output arguments
+ $m->{output} = 0 if ($m->{return} && $m->{name} ne 'result$');
+
+ # link success/return fields to struct/func
+ $s->{success} = $m if defined($m->{success});
+ $s->{return} = $m if ($m->{return});
+ }
+
+ $s->{static} = $static;
+
+ # TODO: default scope? or from struct:<default> etc?
+}
+
+# transfer info from {api} to {data}
+sub processFields {
+ my $api = shift;
+ my $seen = shift;
+ my $obj = shift;
+ my $inc = shift;
+ my $s = shift;
+
+ foreach my $m (@_) {
+ next if $seen->{$m->{name}}++;
+
+ processField($api, $obj, $inc, $s, $m);
+ }
+}
+
+# process a struct/union
+# these have fields as well as potentially other included types
+sub processType {
+ my $api = shift;
+ my $seen = shift;
+ my $obj = shift;
+ my $def = $api->{index}->{"$obj->{type}:<default>"};
+
+ foreach my $s (@_) {
+ my $memberseen = {};
+
+ next if ($seen->{"$s->{type}:$s->{name}"}++);
+
+ print "process type $obj->{match} $s->{type}:$s->{name}\n" if ($api->{vars}->{verbose} > 1);
+
+ # process the struct/union fields first
+ foreach my $inc (grep { $_->{type} eq 'field' } @{$obj->{items}}) {
+ my @list = grep { $_->{name} =~ m/$inc->{regex}/ } @{$s->{items}};
+
+ processFields($api, $memberseen, $obj, $inc, $s, @list);
+ }
+
+ if (optionValue('default', undef, $obj, $def) eq 'all') {
+ print " + adding all fields\n" if ($api->{vars}->{verbose} > 1);
+ processFields($api, $memberseen, $obj, undef, $s, @{$s->{items}});
+ }
+
+ $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $def->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s);
+
+ # finish off
+ postProcessType($s);
+
+ # handle other types included/mark them no-output
+ #$seen->{"$s->{type}:$s->{name}"} = 0;
+ processLibrary($api, $seen, $obj, $s);
+ }
+
+}
+
+sub processLibrary {
+ my $api = shift;
+ my $seen = shift;
+ my $lib = shift;
+ my $ctx = shift;
+ my $data = $api->{data};
+
+ #return if ($seen->{"$ctx->{type}:$ctx->{name}"}++);
+
+ print "process library $ctx->{type}:$ctx->{name}\n" if ($api->{vars}->{verbose} > 0);
+ #print 'lib='.Dumper($lib);
+ #print Dumper($api->{api}); die;
+ #print 'lib.options='.Dumper($lib->{options});
+
+ # TODO: embedded types
+
+ foreach my $inc (@{$lib->{items}}) {
+ print " $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
+
+ if ($inc->{type} =~ m/func|call/on) {
+ my @list = findMatches($api, $inc, $ctx);
+
+ #print 'inc='.Dumper($inc);
+ #print "match $inc->{regex} .options=".Dumper($inc->{options});
+
+ foreach my $s (@list) {
+ if ($seen->{"$s->{type}:$s->{name}"}++) { print "warning: seen $s->{type}:$s->{name}\n"; next; }
+
+ #print " $s->{name}\n";
+ processFunc($api, $lib, $inc, $s);
+
+ #print 'func='.Dumper($s);
+ }
+ } elsif ($inc->{type} eq 'library') {
+ foreach my $l (grep { "$_->{type}:$_->{name}" =~ m/$inc->{regex}/ } @{$api->{api}}) {
+ print " -> $l->{name}\n";
+ # included libraries are never output directly
+ $l->{output} = 0;
+ processLibrary($api, $seen, $l, $ctx);
+ }
+ } elsif ($inc->{type} =~ m/define|enum/on) {
+ # suppress direct output of anything included
+ foreach my $c (findMatches($api, $inc, $ctx)) {
+ $c->{output} = 0;
+ }
+ }
+ }
+
+ if (defined optionValue('import', undef, $lib)) {
+ my @x = map { split /,/,$_ } optionValues('import', $lib);
+ $lib->{imports} = \@x;
+ }
+}
+
+sub postprocess {
+ my $api = shift;
+ my $seen = {};
+ my %data = %{$api->{data}};
+
+ # apply requested options to specific objects (not defaults)
+ foreach my $obj (grep {$_->{type} =~ m/^(func|call|struct|union)$/} @{$api->{api}}) {
+ my @list = findMatches($api, $obj, $obj);
+
+ if ($obj->{type} =~ m/func|call/) {
+ processTypeFunc($api, $seen, $obj, @list);
+ } else {
+ processType($api, $seen, $obj, @list);
+ }
+ }
+
+ # handle libraries
+ foreach my $lib (grep {$_->{type} eq 'library'} @{$api->{api}}) {
+ next if defined($lib->{output});
+ $lib->{output} = 1;
+ processLibrary($api, $seen, $lib, $lib);
+ }
+
+ # apply options for default object types
+ foreach my $obj (grep {$_->{name} eq '<default>'} @{$api->{api}}) {
+ my @list;
+
+ @list = grep { !defined($seen->{"$_->{type}:$_->{name}"}) && $_->{type} eq $obj->{type} } values %data;
+
+ print "apply $obj->{type}:$obj->{name} to ".($#list + 1)." objects\n" if ($#list >= 0);
+
+ if ($obj->{type} =~ m/func|call/) {
+ processTypeFunc($api, $seen, $obj, @list);
+ } else {
+ processType($api, $seen, $obj, @list);
+ }
+ }
+}
+
+1;
--- /dev/null
+# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+
+# method template
+# 'invoke' is a simple string template
+# variables are defined by method.pm
+code method {
+ # normal function invocation
+ invoke {{
+ static final MethodHandle {name}$FH = Memory.downcall("{name}", {function-descriptor});
+ public {static}{java-result} {rename}({java-arguments}) {
+ {native-output-define}
+ {native-result-define}
+ try {create-frame}{
+ {native-output-init}
+ {native-result-assign}{name}$FH.invokeExact({native-call});
+ {native-output-copy}
+ {result-test}{
+ {java-result-assign}
+ {on-success}
+ {java-result-return}
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ {result-throw}
+ }
+ }}
+
+ invoke-dynamic-init {{
+ {name}$FH = Memory.downcall("{name}", {function-descriptor}, resolve, scope);
+ }}
+
+ invoke-dynamic {{
+ final MethodHandle {name}$FH;
+ public {java-result} {rename}({java-arguments}) {
+ {native-output-define}
+ {native-result-define}
+ try {create-frame}{
+ {native-output-init}
+ {native-result-assign}{name}$FH.invokeExact({native-call});
+ {native-output-copy}
+ {result-test}{
+ {java-result-assign}
+ {on-success}
+ {java-result-return}
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ {result-throw}
+ }
+ }}
+
+ # callback function/types
+ downcall {{
+ public static FunctionPointer<{rename}> downcall(MemoryAddress addr$, ResourceScope scope$) {
+ NativeSymbol symbol$ = NativeSymbol.ofAddress("{rename}", addr$, scope$);
+ MethodHandle {rename}$FH = Memory.downcall(symbol$, descriptor());
+ return new FunctionPointer<{rename}>(
+ symbol$,
+ ({java-arguments}) -> {
+ {native-output-define}
+ {native-result-define}
+ try {create-frame}{
+ {native-output-init}
+ {native-result-assign}{rename}$FH.invokeExact({native-call});
+ {native-output-copy}
+ {result-test}{
+ {java-result-assign}
+ {on-success}
+ {java-result-return}
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ });
+ }
+ }}
+
+ upcall {{
+ public static FunctionPointer<{rename}> upcall({rename} target$, ResourceScope scope$) {
+ interface Trampoline {
+ {java-result} call({native-arguments});
+ }
+ Trampoline trampoline = ({native-arguments}) -> {
+ // frame? scope?
+ try (ResourceScope upcallScope$ = ResourceScope.newConfinedScope()) {
+ {trampoline-result-define}target$.call({java-call});
+ {trampoline-result-return}
+ }
+ };
+ return new FunctionPointer<>(
+ Memory.upcall(
+ MethodHandles.lookup(),
+ trampoline,
+ "call",
+ "{java-signature}",
+ descriptor(),
+ scope$),
+ target$);
+ }
+ }}
+}
+
+
+# structs - normal structs
+# handle - anonymous structs
+# library - library template
+
+code class {
+ library {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import au.notzed.nativez.*;
+{imports}
+
+public class {name} {
+{defines}
+{enums}
+{funcs}
+}
+ }}
+ library-dynamic
+ func:template=code:method=invoke-dynamic
+ init:template=code:method=invoke-dynamic-init {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import java.util.function.Function;
+import au.notzed.nativez.*;
+{imports}
+
+public class {name} {
+ {name}(Function<String,MemoryAddress> resolve, ResourceScope scope) {
+{init}
+ }
+ public static {name} create(Function<String,MemoryAddress> resolve, ResourceScope scope) {
+ return new {name}(resolve, scope);
+ }
+{defines}
+{enums}
+{funcs}
+}
+ }}
+ constants {{
+package {package};
+
+public interface {name} {
+{enums}
+{defines}
+}
+ }}
+ struct {{
+package {package};
+import jdk.incubator.foreign.*;
+import jdk.incubator.foreign.MemoryLayout.*;
+import java.lang.invoke.*;
+import au.notzed.nativez.*;
+
+public class {rename} implements Pointer {
+
+ public final MemorySegment segment;
+
+ private {rename}(MemorySegment segment) {
+ this.segment = segment;
+{init}
+ }
+
+ public static {rename} create(MemorySegment segment) {
+ return new {rename}(segment);
+ }
+
+ public static {rename} create(MemoryAddress address, ResourceScope scope) {
+ return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)) : null;
+ }
+
+ public static {rename} create(SegmentAllocator frame) {
+ return create(frame.allocate(LAYOUT));
+ }
+
+ @Override
+ public final MemoryAddress address() {
+ return segment.address();
+ }
+
+ @Override
+ public final ResourceScope scope() {
+ return segment.scope();
+ }
+
+ public final MemorySegment segment() {
+ return segment;
+ }
+
+{defines}
+{enums}
+{layout}
+{accessors}
+{methods}
+{varhandles}
+}
+}}
+ struct-array {{
+package {package};
+import jdk.incubator.foreign.*;
+import jdk.incubator.foreign.MemoryLayout.*;
+import java.lang.invoke.*;
+import au.notzed.nativez.*;
+
+public class {rename} implements Pointer, Array<{rename}> {
+
+ public final MemorySegment segment;
+
+ private {rename}(MemorySegment segment) {
+ this.segment = segment;
+{init}
+ }
+
+ public static {rename} create(MemorySegment segment) {
+ return new {rename}(segment);
+ }
+
+ public static {rename} create(MemoryAddress address, ResourceScope scope) {
+ return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)) : null;
+ }
+
+ public static {rename} create(SegmentAllocator frame) {
+ return create(frame.allocate(LAYOUT));
+ }
+
+ public static {rename} createArray(MemoryAddress address, ResourceScope scope) {
+ return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, Long.MAX_VALUE, scope)) : null;
+ }
+
+ public static {rename} createArray(MemoryAddress address, long length, ResourceScope scope) {
+ return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize() * length, scope)) : null;
+ }
+
+ public static {rename} createArray(long length, SegmentAllocator alloc) {
+ return create(alloc.allocateArray(LAYOUT, length));
+ }
+
+ @Override
+ public final MemoryAddress address() {
+ return segment.address();
+ }
+
+ @Override
+ public final ResourceScope scope() {
+ return segment.scope();
+ }
+
+ public final MemorySegment segment() {
+ return segment;
+ }
+
+ @Override
+ public long length() {
+ return segment.byteSize() / LAYOUT.byteSize();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return segment.byteSize() == 0;
+ }
+
+ @Override
+ public {rename} getAtIndex(long index) {
+ try {
+ return {rename}.create((MemorySegment){name}$SH.invokeExact(segment, index));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+{defines}
+{enums}
+{layout}
+{accessors}
+{methods}
+ final static MethodHandle {name}$SH = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(PathElement.sequenceElement());
+{varhandles}
+}
+}}
+ handle {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import au.notzed.nativez.*;
+
+public class {rename} implements Pointer {
+
+ MemoryAddress address;
+ ResourceScope scope;
+
+ private {rename}(MemoryAddress address, ResourceScope scope) {
+ this.address = address;
+ this.scope = scope;
+{init}
+ }
+
+ public static {rename} create(MemoryAddress address, ResourceScope scope) {
+ return MemoryAddress.NULL != address ? new {rename}(address, scope) : null;
+ }
+
+ public static HandleArray<{rename}> createArray(long count, SegmentAllocator alloc) {
+ return HandleArray.createArray(count, alloc, {rename}::create);
+ }
+
+ @Override
+ public MemoryAddress address() {
+ return address;
+ }
+
+ @Override
+ public ResourceScope scope() {
+ return scope;
+ }
+
+{defines}
+{enums}
+{methods}
+}
+}}
+ call {{
+package {package};
+import jdk.incubator.foreign.*;
+import java.lang.invoke.*;
+import au.notzed.nativez.*;
+
+@FunctionalInterface
+public interface {rename} {
+ {java-result} call({java-arguments});
+
+ public static FunctionDescriptor descriptor() {
+ return {function-descriptor};
+ }
+
+{upcall}
+{downcall}
+}
+}}
+}
+
+# do I want this to be perl code as template or just a template?
+code getset {
+ get set=value={getnative} {{
+ public {type} get{rename}() {
+ return {tojava};
+ }
+ }}
+ geti set=value={getnative} set=segment=segment {{
+ # TODO: final static VarHandle {name}$VI = MemoryLayout.sequenceLayout(LAYOUT).varHandle(PathElement.sequenceElement(), PathElement.groupElement("{name}"));
+ public {type} get{rename}AtIndex(long index) {
+ // option a: resolve an offset segment and asme as above with set=segment=segment
+ // option b: an indexed varhandle
+ MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize());
+ return {tojava};
+ }
+ }}
+ set set=value=value {{
+ public void set{rename}({type} value) {
+ {setnative};
+ }
+ }}
+ seti set=value=value set=segment=segment {{
+ public void set{rename}AtIndex(long index, {type} value) {
+ MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize());
+ {setnative};
+ }
+ }}
+}
+
+# set requires allocations, not sure if it should be Frame or Scope?
+code getset-frame {
+ get set=value={getnative} {{
+ public {type} get{rename}() {
+ return {tojava};
+ }
+ }}
+ geti set=value={getnative} set=segment=segment {{
+ public {type} get{rename}AtIndex(long index) {
+ // option a: resolve an offset segment and asme as above with set=segment=segment
+ // option b: an indexed varhandle
+ MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize());
+ return {tojava};
+ }
+ }}
+ set set=value=value {{
+ public void set{rename}(Frame frame$, {type} value) {
+ {setnative};
+ }
+ }}
+ seti set=value=value set=segment=segment {{
+ public void set{rename}AtIndex(Frame frame$, long index, {type} value) {
+ MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize());
+ {setnative};
+ }
+ }}
+}
+
+code getsetelement {
+ get set=index=i {{
+ public {typei} get{rename}Element(long i) {
+ return {getnativei};
+ }
+ }}
+ set set=index=i set=value=value {{
+ public void set{rename}Element(long i, {typei} value) {
+ {setnativei};
+ }
+ }}
+}
+
+code getsetelement2d {
+ get set=index0=i set=index1=j {{
+ public {typei} get{rename}Element(long i, long j) {
+ return {getnativei};
+ }
+ }}
+ set set=index0=i set=index1=j set=value=value {{
+ public void set{rename}Element(long i, long j, {typei} value) {
+ {setnativei};
+ }
+ }}
+}
+
+code getbyvalue {
+ get {{
+ public {type} get{rename}() {
+ try {
+ return {getnative};
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+ }}
+}
--- /dev/null
+package code;
+
+use strict;
+
+use File::Path qw(make_path);
+use File::Basename;
+use Data::Dumper;
+use List::Util qw(first);
+
+require api;
+
+my %typeSizes = (
+ i8 => 'byte', u8 => 'byte',
+ i16 => 'short', u16 => 'short',
+ i32 => 'int', u32 => 'int',
+ i64 => 'long', u64 => 'long',
+ f32 => 'float',
+ f64 => 'double',
+);
+
+my %typeSuffix = (
+ 'long' => 'L',
+ 'float' => 'f'
+);
+
+my %defineType = (
+ %typeSizes,
+ string => 'String'
+);
+
+my %typePrefix = (
+ i8 => '(byte)',
+ u8 => '(byte)',
+ i16 => '(short)',
+ u16 => '(short)',
+ string => '"'
+);
+
+my %typeSuffix = (
+ u64 => 'L',
+ i64 => 'L',
+ f32 => 'f',
+ string => '"'
+);
+
+my %typeSignature = (
+ 'byte' => 'B',
+ 'short' => 'S',
+ 'int' => 'I',
+ 'long' => 'J',
+ 'float' => 'F',
+ 'double' => 'D',
+ 'void' => 'V',
+ 'MemorySegment' => 'Ljdk/incubator/foreign/MemorySegment;',
+ 'MemoryAddress' => 'Ljdk/incubator/foreign/MemoryAddress;',
+);
+
+# creates per-field type info, used by methods and structs
+# the names are bit naff here
+sub scanFields {
+ my $api = shift;
+ my $s = shift;
+ my $data = $api->{data};
+
+ my @members = ();
+ foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) {
+ my $info = $api->findType($m);
+ my $type = $info->{type};
+ my $match = $info->{match};
+ my $typeSizes = $code::typeSizes;
+
+ foreach my $k (keys %{$type->{items}}) {
+ my $f = $type->{items}->{$k};
+
+ if (defined $f) {
+ my $e = eval $f;
+ if (!defined $e) {
+ print "$typeSizes{$match->{ctype}}\n";
+
+ print "$s->{name} field=$m->{name} deref=$m->{deref} regex=$type->{regex} $k=\n$f\n";
+ print "match=".Dumper($match);
+ print "error: $! $@\n";
+ die;
+ } else {
+ #print "$m->{name} $type->{regex} $k = $f = $e\n";
+ }
+ $match->{$k} = $e;
+ }
+ }
+
+ # hmm this should probably be in loop above, but there's clashes with things like {type}
+ foreach my $o (grep { defined $m->{$_} } 'tonative') {
+ $match->{$o} = findCode($api, $m->{$o});
+ }
+
+ $match->{name} = $m->{name};
+ $match->{rename} = $m->{rename};
+ $match->{segment} = 'segment()';
+ $match->{scope} = 'scope()';
+
+ push @members, { field=>$m, type=>$type, match=>$match };
+ }
+ @members;
+}
+
+sub findCode {
+ my $api = shift;
+ my $v = shift;
+
+ if ($v =~ m/^(code:.+)=(.*)$/) {
+ my $t = $api->{index}->{$1};
+ my $l = api::findItem($t, $2);
+
+ die "Uknown template '$1.$2'" if !defined($t) || !defined($l);
+ $l->{literal};
+ } else {
+ $v;
+ }
+}
+
+sub formatFunctionDescriptor {
+ my $api = shift;
+ my $members = shift;
+ my $d;
+
+ if ($members->[0]->{field}->{deref} eq 'void') {
+ shift @$members;
+ $d = "FunctionDescriptor.ofVoid(";
+ } else {
+ $d = "FunctionDescriptor.of(";
+ }
+
+ $d .= join(', ', map { formatTemplate($_->{match}->{layout}, $_->{match}) } @$members);
+ $d .= ')';
+
+ return $d;
+}
+
+sub formatFunctionSignature {
+ my $api = shift;
+ my $members = shift;
+ my $result = shift @$members;
+
+ return '('.join('', map { $typeSignature{$_->{match}->{carrier}} } @$members).')'
+ .$typeSignature{$result->{match}->{carrier}};
+}
+
+sub formatStructLayout {
+ my $api = shift;
+ my $s = shift;
+ my $members = shift;
+ my $count = 0;
+ my $lastOffset = 0;
+ my $maxSize = 8;
+ my $layout;
+
+ $layout = "\tpublic static final GroupLayout LAYOUT = MemoryLayout.$s->{type}Layout(\n\t\t";
+
+ foreach my $i (@$members) {
+ my $m = $i->{field};
+ my $type = $i->{type};
+ my $match = $i->{match};
+
+ $maxSize = bitfieldSize($m) if (bitfieldSize($m) > $maxSize);
+
+ if ($match->{layout}) {
+ if ($m->{offset} > $lastOffset) {
+ $layout .= ",\n\t\t" if $count++;
+ $layout .= 'MemoryLayout.paddingLayout('.($m->{offset} - $lastOffset).')';
+ }
+ $layout .= ",\n\t\t" if $count++;
+ $layout .= formatTemplate($match->{layout}, $match).".withName(\"$m->{name}\")";
+ $lastOffset = $m->{offset} + $m->{size};
+ }
+ }
+ if ($s->{size} > $lastOffset) {
+ $layout .= ",\n\t\t" if ($count++ > 0);
+ $layout .= 'MemoryLayout.paddingLayout('.($s->{size} - $lastOffset).')';
+ }
+
+ $layout .= "\n\t).withBitAlignment($maxSize);\n";
+ $layout;
+}
+
+sub formatDefine {
+ my $api = shift;
+ my $s = shift;
+
+ my $d = join "\n\t", map {
+ my $type = $_->{type};
+ my $name = $_->{name};
+ my $value = $_->{value};
+
+ "public static final $defineType{$type} $name = $typePrefix{$type}$value$typeSuffix{$type};";
+ } @{$s->{items}};
+
+ return "\t$d\n";
+}
+
+sub formatEnum {
+ my $api = shift;
+ my $s = shift;
+ my $seen = shift;
+ my $type = $defineType{$s->{value_type}};
+
+ my $d = join "\n\t", "// enum $s->{name}", map {
+ my $name = $_->{name};
+ my $value = $_->{value};
+
+ "public static final $type $name = $typePrefix{$type}$value$typeSuffix{$type};";
+ } grep {
+ $seen->{"value:$_->{name}"}++ == 0
+ } @{$s->{items}};
+
+ return "\t$d\n";
+}
+
+# somewhat faster
+# dunno how to ignore comments tho
+sub formatTemplate {
+ my $template = shift;
+ my $vars = shift;
+ my $prefix = shift;
+ my $result;
+
+ while ($template =~ m/^(.*?)\{([\w-]+)\}/spo) {
+ $result .= $1;
+
+ if (defined $vars->{$2}) {
+ $template = $vars->{$2}.${^POSTMATCH};
+ } else {
+ $result .= "{$2}";
+ $template = ${^POSTMATCH};
+ }
+ }
+ $result .= $template;
+ $result =~ s/^/$prefix/gm;
+ $result;
+}
+
+# Format a template without recursive application
+sub formatTemplateStream {
+ my $template = shift;
+ my $vars = shift;
+ my $prefix = shift;
+ my $result;
+
+ while ($template =~ m/^(.*?)\{([\w-]+)\}/spo) {
+ $result .= $1;
+
+ if (defined $vars->{$2}) {
+ $result .= $vars->{$2};
+ } else {
+ $result .= "{$2}";
+ }
+ $template = ${^POSTMATCH};
+ }
+ $result .= $template;
+ $result =~ s/^/$prefix/gm;
+ $result;
+}
+
+# takes a template entry applies options and then formats it
+# applyTemplate(template-item, vars)
+sub applyTemplate {
+ my $code = shift;
+ my $match = shift;
+ my $stream = shift;
+ my $vars = \%{$match};
+
+ #print "template: $code->{match}\n";
+
+ foreach my $set (api::optionValuesAll('set', $code)) {
+ $vars->{$1} = $2 if ($set =~ m/^(\w+)=(.*)/);
+ }
+
+ $stream ? formatTemplateStream($code->{literal}, $vars) : formatTemplate($code->{literal}, $vars);
+}
+
+sub bitfieldSize {
+ my $m = shift;
+ my $size = $m->{size};
+
+ return 64 if ($size > 32);
+ return 32 if ($size > 16);
+ return 16 if ($size > 8);
+ return 8;
+}
+
+sub bitfieldType {
+ my $m = shift;
+ my $size = $m->{size};
+ return 'long' if ($size > 32);
+ return 'int' if ($size > 16);
+ return 'short' if ($size > 8);
+ return 'byte';
+}
+
+sub bitfieldIndex {
+ use integer;
+ my $m = shift;
+
+ $m->{offset} / bitfieldSize($m);
+}
+
+sub bitfieldOffset {
+ use integer;
+ my $m = shift;
+
+ $m->{offset} & (bitfieldSize($m) - 1);
+}
+
+sub bitfieldMask {
+ use integer;
+ my $m = shift;
+
+ sprintf("0x%x", ((1 << $m->{size}) - 1) << bitfieldOffset($m)).$typeSuffix{bitfieldType($m)};
+}
+
+1;
--- /dev/null
+package config;
+
+use File::Basename;
+use strict;
+
+require tokenise;
+
+#
+
+# parser for control file
+
+# format:
+# type name (token) * {
+# ( match (params) *
+# ( {{ literal }} | ; )
+# )*
+# }
+# or
+# type name (token *) {{
+# literal
+# }}
+
+# tokens are separated by lwsp or ';'
+
+sub new {
+ my $class = shift;
+ my $options = shift;
+ my $self = {
+ options => $options,
+ objects => [],
+ includes => [],
+ pragmas => [],
+ };
+
+ foreach my $path (@_) {
+ loadControlFile($self, $path);
+ }
+
+ bless $self, $class;
+ return $self;
+}
+
+# find first occurance of flag
+# name, ...objects
+sub optionFlag {
+ my $name = shift;
+
+ foreach my $obj (@_) {
+ foreach my $opt (@{$obj->{options}}) {
+ return 1 if ($opt eq $name);
+ }
+ }
+ undef;
+}
+
+# find value of first option of the given name
+# name, ...objects
+sub optionValue {
+ my $name = shift;
+ my $or = shift;
+
+ foreach my $obj (@_) {
+ foreach my $opt (@{$obj->{options}}) {
+ return $1 if ($opt =~m/^\Q$name\E=(.*)$/);
+ }
+ }
+ $or;
+}
+
+sub findInclude {
+ my $self = shift;
+ my $file = shift;
+
+ foreach my $dir (@{$self->{options}->{include}}) {
+ my $path = "$dir/$file";
+ return $path if -e $path;
+ }
+
+ return $file;
+}
+
+sub loadControlFile {
+ my $self = shift;
+ my $path = shift;
+ my $list = $self->{objects};
+ my $target;
+ my $item;
+ my $literal;
+ my $state = 0;
+ my @states;
+ my $tokeniser = new tokenise($path);
+
+ while (my $t = $tokeniser->next()) {
+ #print " $state $t\n";
+ if ($state == 0) {
+ if ($t =~ m/^%/) {
+ my @pragma = ( $t );
+ my $a = $tokeniser->next();
+
+ while (defined($a) && $a ne ';') {
+ push @pragma, $a;
+ $a = $tokeniser->next();
+ }
+
+ if ($t eq '%include') {
+ my $file = findInclude($self, $pragma[1]);
+
+ print "including $file\n";
+
+ push @{$self->{includes}}, $file;
+ $tokeniser->include($file);
+ } else {
+ push @{$self->{pragmas}}, \@pragma;
+ }
+ } elsif ($tokeniser->{type} eq 'token') {
+ $target = { type => $t, options => [], items => [], file => "$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}" };
+ push @$list, $target;
+ $state = 1;
+ } else {
+ die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expected type token");
+ }
+ } elsif ($state == 1) {
+ # token [token]
+ if ($tokeniser->{type} eq 'token') {
+ $target->{name} = $t;
+ $state = 2;
+ } else {
+ die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expected match token");
+ }
+ } elsif ($state == 2) {
+ # token token token* [ '{' | literal | ';' ]
+ if ($t eq '{') {
+ $state = 3;
+ } elsif ($t eq ';') {
+ $state = 0;
+ } elsif ($tokeniser->{type} eq 'literal') {
+ $target->{literal} = stripLiteral($t);
+ $state = 0;
+ } elsif ($tokeniser->{type} eq 'token') {
+ push @{$target->{options}}, $t;
+ } else {
+ die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expecting {, ;, or {{literal}} or option: not '$t'");
+ }
+ } elsif ($state == 3) {
+ if ($t eq '}') {
+ $state = 0;
+ } elsif ($tokeniser->{type} eq 'token') {
+ $item = { match => $t, options => [] };
+ push @{$target->{items}}, $item;
+ $state = 4;
+ } else {
+ die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expecting } or match token");
+ }
+ } elsif ($state == 4) {
+ if ($t eq ';') {
+ $state = 3;
+ } elsif ($tokeniser->{type} eq 'token') {
+ push @{$item->{options}}, $t;
+ } elsif ($tokeniser->{type} eq 'literal') {
+ $item->{literal} .= stripLiteral($t);
+ $state = 3;
+ } else {
+ die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: not expecting type=$tokeniser->{type} value=$t");
+ }
+ }
+ }
+ die "parse error in '$path' state=$state" if $state;
+}
+
+sub stripLiteral {
+ my $t = shift;
+
+ if ($t =~ m/\n/s) {
+ $t =~ s/^\s*\n//s;
+ $t =~ s/\n\s+$/\n/s;
+ } else {
+ $t =~ s/^\s+//;
+ $t =~ s/\s+$//;
+ }
+ $t;
+}
+
+1;
--- /dev/null
+
+package method;
+
+use strict;
+use Data::Dumper;
+
+require code;
+
+sub fieldScope {
+ my $m = shift;
+
+ if ($m->{scope} =~ m/^explicit/) {
+ 'scope$';
+ } elsif ($m->{scope} eq 'instance') {
+ 'scope()';
+ } else {
+ 'ResourceScope.globalScope()';
+ }
+}
+sub fieldScopeAction {
+ my $m = shift;
+
+ # TBH this just isn't going to work, scopes are so limited in use
+ return ();
+
+ if ($m->{field}->{scope} =~ m/^explicit,(.*)$/) {
+ return code::formatTemplate("scope\$.addCloseAction(() -> $1;", { %{$m->{match}}, value => 'res$' });
+ } elsif ($m->{field}->{scope} =~ m/^explicit$/) {
+ return code::formatTemplate('scope$.addCloseAction(() -> close({name}));', $m->{match});
+ } else {
+ ();
+ }
+}
+sub apply {
+ my $t = shift;
+ my $m = shift;
+ return code::formatTemplate($t, $m->{match});
+}
+
+sub new {
+ my $class = shift;
+ my $api = shift;
+ my $c = shift;
+ my @members = code::scanFields($api, $c);
+ my $result = shift @members;
+
+ my $self = {
+ result => $result,
+ arguments => \@members,
+ vars => \%{$api->{vars}},
+ method => $c,
+ };
+ my $info = $self->{vars};
+
+ $info->{name} = $c->{name};
+ $info->{rename} = $c->{rename};
+
+ my @list = map { apply('{type} {name}', $_) } grep { $_->{field}->{output} } @members;
+ push @list, 'ResourceScope scope$' if ($c->{scope} =~ m/explicit/);
+ $info->{'java-arguments'} = join ', ', @list;
+ $info->{'native-arguments'} = join ', ', map { apply('{carrier} {name}', $_) } @members;
+
+ # for native downcalls
+ $info->{'native-result-define'} = ($result->{match}->{type} ne 'void') ? apply('{carrier} result$;', $result) : '';
+ $info->{'native-result-assign'} = ($result->{match}->{type} ne 'void') ? apply('result$ = ({carrier})', $result) : '';
+ $info->{'native-call'} = join ', ', map {
+ my $m = $_->{field};
+
+ if ($m->{instance}) {
+ "(jdk.incubator.foreign.Addressable)address()";
+ } elsif ($m->{implied}) {
+ $m->{implied};
+ } else {
+ my $name = $_->{match}->{name};
+
+ if ($m->{output} == 0 && ($m->{deref} =~ m/^u64:/)) {
+ $name .= '$h';
+ } elsif (defined($m->{'array-size-source'})) {
+ # or some function?
+ $name = "Memory.size($m->{'array-size-source'}->{name})";
+ }
+ code::formatTemplate("{tonative}", { %{$_->{match}}, value=>$name })
+ }
+ } @members;
+
+ if ($result->{field}->{deref} =~ m/^\$\{\w+\}$/) {
+ # non-primitive return, args requires a segmentallocator
+ $info->{'native-call'} = 'alloc$, '.$info->{'native-call'};
+ $info->{'java-arguments'} = $info->{'java-arguments'} ?
+ $info->{'java-arguments'}.', SegmentAllocator alloc$'
+ :'SegmentAllocator alloc$';
+ }
+
+ # for java upcalls, they have an explicit scope always
+ $info->{'java-call'} = join ', ', map { code::formatTemplate('{tojava}', { %{$_->{match}}, value=>'{name}', scope=>'upcallScope$' }) } grep { $_->{field}->{output} } @members;
+
+ $info->{'java-signature'} = code::formatFunctionSignature($api, [$result, @members]);
+ $info->{'function-descriptor'} = code::formatFunctionDescriptor($api, [$result, @members]);
+
+ # hidden output arguments
+ # TODO: what about in/out arguments? they just fall out by not specifying them as scoped?
+ my @output = grep {
+ !$_->{field}->{implied}
+ && $_->{field}->{output} == 0
+ && ($_->{field}->{deref} =~ m/^u64:/)
+ && $_->{field}->{instance} == 0
+ } @members;
+
+ $info->{'native-output-define'} = join "\n\t\t", map { apply('{carrieri} {name};', $_) } @output;
+ $info->{'native-output-init'} = join ";\n\t\t\t", map { apply('{type} {name}$h = {type}.createArray(1, frame$);', $_) } @output;
+ $info->{'native-output-copy'} = join ";\n\t\t\t", map { apply('{name} = {name}$h.get(0);', $_) } @output;
+
+ # also required for some tonative types?
+ my $x = grep { $_->{match}->{type} eq 'String' } @members;
+ $info->{'create-frame'} = ($#output >= 0 || grep {
+ $_->{match}->{type} eq 'String' || $_->{field}->{'raw-in'};
+ } @members) ? '(Frame frame$ = Frame.frame()) ' : '';
+
+ # result code handling
+ if ($c->{success}) {
+ $info->{'result-code'} = $c->{success}->{name};
+
+ # success test
+ if ($c->{success}->{success} eq '!null') {
+ $info->{'result-test'} = "if ($c->{success}->{name} != MemoryAddress.NULL) ";
+ $info->{'result-throw'} = 'throw new NullPointerException();';
+ } else {
+ $info->{'result-test'} = 'if ('.join('||', map { "$c->{success}->{name} == $_" } split(/,/,$c->{success}->{success})).') ';
+ $info->{'result-throw'} = 'throw new RuntimeException("error="+'.$c->{success}->{name}.');';
+ }
+ } else {
+ $info->{'result-test'} = '';
+ $info->{'result-throw'} = '';
+ }
+
+ # success actions
+ my @onsuccess = ();
+
+ push @onsuccess, code::findCode($api, $c->{onsuccess}) if defined($c->{onsuccess});
+
+ if (defined($c->{return})) {
+ # nb: this is only used for output parameters
+ my $res = (grep { $_->{field} == $c->{return} } $result, @members)[0];
+
+ $info->{'java-result'} = $res->{match}->{typei};
+ push @onsuccess, fieldScopeAction($res);
+ #push @onsuccess, code::formatTemplate('return {tojavai};', { %{$res->{match}}, value => $res->{field}->{name}, scope=>fieldScope($res->{field}) });
+
+ $info->{'java-result-assign'} = code::formatTemplate('{typei} res$ = {tojavai};', { %{$res->{match}}, value => $res->{field}->{name}, scope=>fieldScope($res->{field}) });
+ $info->{'java-result-return'} = 'return res$;';
+ $info->{'trampoline-result-define'} = 'error';
+ $info->{'trampoline-result-return'} = 'error';
+ } elsif ($result->{field}->{output} && $result->{match}->{type} ne 'void') {
+ $info->{'java-result'} = $result->{match}->{type};
+ push @onsuccess, fieldScopeAction($result);
+
+ $info->{'java-result-assign'} = code::formatTemplate('{type} res$ = {tojava};', { %{$result->{match}}, value => 'result$', scope=>fieldScope($result->{field}) });
+ $info->{'java-result-return'} = 'return res$;';
+
+ $info->{'trampoline-result-define'} = apply('{type} res$ = ', $result);
+ $info->{'trampoline-result-return'} = code::formatTemplate('return ({type}){tonative};', { %{$result->{match}}, value => 'res$' });
+ } else {
+ $info->{'java-result'} = 'void';
+ $info->{'java-result-assign'} = '';
+ $info->{'java-result-return'} = 'return;';
+ $info->{'trampoline-result-define'} = '';
+ $info->{'trampoline-result-return'} = '';
+ }
+ $info->{'on-success'} = join("\n\t\t\t\t", @onsuccess);
+
+ $info->{'static'} = $c->{static} ? 'static ' : '';
+
+ bless $self, $class;
+ $self;
+}
--- /dev/null
+
+#
+# simple tokeniser
+#
+# tokens are
+# 'self' : '{' ';' '}'
+# 'token':'\S+'
+# 'literal': '{{.*}}\n' may span multiple lines but final }}
+# must be at end of line
+
+package tokenise;
+
+use Data::Dumper;
+use strict;
+
+sub new {
+ my $class = shift;
+ my $path = shift;
+ my $self = {
+ state => 0,
+ line => [],
+ colno => 0,
+ type => 'none',
+ files => [],
+ file => { path=>$path, lineno=>0, lines=>[] }
+ };
+
+ open (my $in,"<",$path) || die("unable to open '$path': $@");
+ @{$self->{file}->{lines}} = <$in>;
+ close $in;
+
+ bless $self, $class;
+ return $self;
+}
+
+# include a file at the current point
+sub include {
+ my $self = shift;
+ my $path = shift;
+
+ #print "include '$path'\n";
+
+ push @{$self->{files}}, $self->{file};
+
+ $self->{file} = { path=>$path, lineno=>0, lines=>[] };
+
+ open (my $in,"<",$path) || die("unable to open '$path': $@");
+ @{$self->{file}->{lines}} = <$in>;
+ close $in;
+
+ $self->{state} = 0;
+ # should it swallow the rest of {line}?
+}
+
+sub next {
+ my $self = shift;
+ my $token;
+
+ #print "next $self->{state} $#{$self->{lines}} $#{$self->{line}}\n";
+ # on entry state is in [0, 3, 4]
+ while ($#{$self->{file}->{lines}} >= 0 || $#{$self->{line}} >= 0 || $#{$self->{files}} >= 0) {
+ if ($#{$self->{line}} < 0) {
+ if ($#{$self->{file}->{lines}} < 0) {
+ $self->{file} = pop @{$self->{files}};
+ }
+ my $t = shift @{$self->{file}->{lines}};
+ $t =~ s/(?:^\#.*|\s+\#.*)//;
+ push @{$self->{line}}, split //,$t;
+ $self->{file}->{lineno} += 1;
+ $self->{colno} = 0;
+ }
+ my $c = shift @{$self->{line}};
+
+ $self->{colno} += 1;
+
+ #printf("$self->{state} '$c'=\$%02x\n", ord($c));
+
+ if ($self->{state} == 0) {
+ if (($c =~ m/\s/)) {
+ next;
+ } elsif ($c =~ m/[;\}]/) {
+ $self->{type} = $c;
+ #print "char: $c\n";
+ return $c;
+ } elsif ($c eq '{') {
+ if ($#{$self->{line}} >= 0 && $self->{line}->[0] eq '{') {
+ shift @{$self->{line}};
+ $self->{state} = 3;
+ } else {
+ $self->{type} = $c;
+ #print "char: $c\n";
+ return $c;
+ }
+ } elsif ($c) {
+ $token .= $c;
+ $self->{state} = 1;
+ }
+ } elsif ($self->{state} == 1) {
+ if ($c eq ';') {
+ unshift @{$self->{line}}, $c;
+ $self->{state} = 2;
+ $self->{type} = 'token';
+ #print "tok: $token\n";
+ return $token;
+ } elsif ($c =~ m/\S/) {
+ $token .= $c;
+ } else {
+ $self->{state} = 0;
+ $self->{type} = 'token';
+ #print "tok: $token\n";
+ return $token;
+ }
+ } elsif ($self->{state} == 2) {
+ $self->{state} = 0;
+ #print "char: $c\n";
+ return $c;
+ } elsif ($self->{state} == 3) {
+ if ($c eq '}' && $#{$self->{line}} == 1 && $self->{line}->[0] eq '}') {
+ shift @{$self->{line}};
+ $self->{state} = 0;
+ $self->{type} = 'literal';
+ #print "lit: $token\n";
+ return $token;
+ } else {
+ $token .= $c;
+ }
+ }
+ }
+
+ die "invalid state=$self->{state}" if $self->{state} != 0;
+
+ return undef;
+}
+
+1;
--- /dev/null
+# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*-
+
+#
+# TODO: use select=array rather than inline logic to select alternatives? maybe?
+
+# special case for bitfields
+# TODO: well this is bit of a mess, maybe should use <init> like code:method?
+type /bitfield/ copy=<common> {
+ layout;
+ type {{ bitfieldType($m) }}
+ carrier {{ "{type}" }}
+ tonative {{ "({type})({value})" }}
+ tojava {{ "({type})({value})" }}
+
+ getshiftl {{ bitfieldSize($m) - $m->{size} - bitfieldOffset($m) }}
+ getshiftr {{ bitfieldSize($m) - $m->{size} }}
+ getshiftop {{ ($m->{type} =~ m/^i/) ? ">>" : ">>>" }}
+
+ setmask {{ bitfieldMask($m) }}
+ setoffset {{ bitfieldOffset($m) }}
+
+ varindex {{ bitfieldIndex($m) }}
+
+ getnative {{
+ "({type})((({type}){name}\$VH.get({segment}) << {getshiftl}) {getshiftop} {getshiftr})"
+ }}
+ setnative {{
+ "$m->{name}\$VH.set({segment}, ({type})((({type}){name}\$VH.get({segment}) & ~{setmask}) | ((({value}) << {setoffset}) & {setmask})))"
+ }}
+ varhandle {{ "final static VarHandle {name}\$VH = MemoryLayout.sequenceLayout(Memory.".uc(bitfieldType($m)).").varHandle(MemoryLayout.PathElement.sequenceElement({varindex}));\n" }}
+}
+
+# void, only for return type
+type /void/ {
+ type {{ 'void' }}
+ carrier {{ 'void' }}
+ getnative;
+ setnative;
+ tonative;
+ tojava;
+}
+
+# 1d array of pointer to pointer
+type /^\[(\d+)u64:u64.*\]$/ {
+}
+
+# 1d array of pointer to primitive
+type /^\[(?<length>\d+)u64:(?<ctype>[uif]\d+)\]$/
+ copy=<pointer> template=code:getbyvalue,code:getsetelement {
+
+ type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }}
+ layout {{ 'MemoryLayout.sequenceLayout({length}, Memory.POINTER)' }}
+ tojava {{ !$m->{array} ? 'PointerArray.createArray({value}, {length}, {scope})' : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})' }}
+
+ getsegment {{ '(MemorySegment){name}$SH.invokeExact({segment})' }}
+ getnative {{ !$m->{array} ? 'PointerArray.create({getsegment})' : 'HandleArray.create({getsegment}, (a, s) -> {typei}.createArray(a, Long.MAX_VALUE, s))' }}
+ setnative;
+ varhandle {{
+ 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n".
+ 'final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement());'."\n"
+ }}
+
+ typei {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }}
+ getnativei {{ '{typei}.createArray((MemoryAddress){name}$EH.get({segment}, {index}), Long.MAX_VALUE, {scope})' }}
+ setnativei {{ '{name}$EH.set({segment}, {index}, (Addressable)Memory.address({value}))' }}
+
+}
+
+# 1d array of pointer to type (struct or union or function?)
+type /^\[(?<length>\d+)u64:\$\{(\w+)\}\]$/ {
+ type {{ "HandleArray<$data->{$m->{type}}->{rename}>" }}
+ layout {{ "MemoryLayout.sequenceLayout({length}, Memory.POINTER)" }}
+}
+
+# 1d array of (struct or union or function?)
+type /^\[(?<length>\d+)\$\{(\w+)\}\]$/ template=code:getbyvalue {
+ type {{ "$data->{$m->{type}}->{rename}" }}
+ layout {{ "MemoryLayout.sequenceLayout({length}, {type}.LAYOUT)" }}
+
+ getsegment {{ '(MemorySegment){name}$SH.invokeExact({segment})' }}
+ getnative {{ '{type}.create({getsegment})' }}
+
+ varhandle {{
+ 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n".
+ '//final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement());'."\n"
+ }}
+
+}
+
+# 1d array of primitive
+type /^\[(?<length>\d+)(?<ctype>[uif]\d+)\]$/
+ copy=<pointer> template=code:getbyvalue,code:getsetelement {
+ layout {{ "MemoryLayout.sequenceLayout({length}, Memory.".uc($typeSizes{$match->{ctype}}).")" }}
+ type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+ tojava {{ "{type}.createArray({value}, $match->{length}, {scope})" }}
+ tonative {{ "(Addressable)Memory.address({value})" }}
+ lea {{ "(MemorySegment)$m->{name}\$SH.invokeExact({segment})" }}
+ getnative {{ "{type}.create({lea})" }}
+ setnative;
+ varhandle {{
+ "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n".
+ "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement());\n"
+ }}
+
+ typei {{ "$typeSizes{$match->{ctype}}" }}
+ getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index})" }}
+ setnativei {{ "$m->{name}\$EH.set({segment}, {index}, ({typei}){value})" }}
+}
+
+# 2d array of primitive
+type /^\[(?<length0>\d+)\[(?<length1>\d+)(?<ctype>[uif]\d+)\]\]$/
+ copy=<pointer> template=code:getbyvalue,code:getsetelement2d {
+ layout {{ "MemoryLayout.sequenceLayout({length0}, MemoryLayout.sequenceLayout({length1}, Memory.".uc($typeSizes{$match->{ctype}})."))" }}
+ type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+ tojava {{ "{type}.create({value})" }}
+ tonative {{ "(Addressable)Memory.address({value})" }}
+
+ getnative {{ "{type}.create((MemorySegment)$m->{name}\$SH.invokeExact({segment}))" }}
+ setnative;
+
+ typei {{ "$typeSizes{$match->{ctype}}" }}
+ getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index0}, {index1})" }}
+ setnativei {{ "$m->{name}\$EH.set({segment}, {index0}, {index1}, ({typei}){value})" }}
+
+ varhandle {{
+ "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n".
+ "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.sequenceElement());\n"
+ }}
+}
+
+# any pointer with 'raw' option
+type /^u64:/ select=raw copy=<pointer> {
+}
+
+# pointer is an 'in' only pointer to pointer
+type /^u64:u64:/ select=raw-in copy=<pointer> {
+ tonative {{ '(Addressable)frame$.allocate(Memory.POINTER, {value})' }}
+}
+
+# function pointer
+type /^u64:\(/ copy=<pointer> {
+ type {{ "FunctionPointer<$data->{$m->{type}}->{rename}>" }}
+ tojava {{ "$data->{$m->{type}}->{rename}.downcall({value}, {scope})" }}
+}
+
+# **primitive
+type /^u64:u64:(?<ctype>[uif]\d+)$/ copy=<pointer> {
+ length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }}
+ type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }}
+ tojava {{
+ !$m->{array}
+ ? 'PointerArray.createArray({value}, {length}, {scope})'
+ : 'HandleArray.createArray({value}, {length}, (a, s) -> {typei}.createArray(a, {length}, s), {scope})'
+ }}
+ typei {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+}
+
+# **type
+type /^u64:u64:\$\{(\w+)\}$/ copy=<pointer> {
+ length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }}
+ type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }}
+ tojava {{
+ !$m->{array}
+ ? 'PointerArray.createArray({value}, {length}, {scope})'
+ : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})'
+ }}
+ # tojavai ... ?
+ carrieri {{ "MemoryAddress" }}
+ typei {{ "$data->{$m->{type}}->{rename}" }}
+ tojavai {{ "{typei}.create({value}, {scope})" }}
+}
+
+# **void
+type /^u64:u64:v$/ copy=<pointer> {
+ length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }}
+ type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<PointerArray>' }}
+ tojava {{
+ !$m->{array}
+ ? 'PointerArray.createArray({value}, {length}, {scope})'
+ : 'HandleArray.createArray({value}, {length}, PointerArray::create, {scope})'
+ }}
+}
+
+# *primitive, or string for [iNNi8]
+# TODO: length, multiple flags?
+type /^u64:(?<ctype>[ui]8)$/ select=array copy=<pointer> {
+ type {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }}
+ tojava {{ '{type}.createArray({value}, Long.MAX_VALUE, {scope})' }}
+ carrieri {{ "{typei}" }}
+ typei {{ $typeSizes{$match->{ctype}} }}
+ tojavai {{ "({typei}){value}" }}
+}
+
+# *i8 with 'segment' flag, length must be supplied (somewhere??)
+type /^u64:(?<ctype>[ui]8)$/ select=segment copy=<pointer> {
+ type {{ 'MemorySegment' }}
+ tojava {{ '{value}' }}
+ carrieri {{ "{typei}" }}
+ typei {{ 'byte' }}
+ tojavai {{ "({typei}){value}" }}
+}
+
+# *i8 with no flag = String
+type /^u64:(?<ctype>i8)$/ copy=<pointer> {
+ type {{ 'String' }}
+ tonative {{ '(Addressable)({value} != null ? frame$.copy({value}) : MemoryAddress.NULL)' }}
+
+ setnative {{ '{name}$VH.set({segment}, {copynative})' }}
+ copynative {{ 'SegmentAllocator.nativeAllocator(segment.scope()).allocateUtf8String({value})' }}
+
+ tojava {{ '({value}).getUtf8String(0L)' }}
+}
+
+# *Primitive fallback
+type /^u64:(?<ctype>[uif]\d+)$/ copy=<pointer> {
+ type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }}
+ tonative {{ '(Addressable)Memory.address({value})' }}
+ tojava {{ ucfirst($typeSizes{$match->{ctype}})."Array.createArray({value}, Long.MAX_VALUE, {scope})" }}
+ carrieri {{ "{typei}" }}
+ typei {{ $typeSizes{$match->{ctype}} }}
+ tojavai {{ "({typei}){value}" }}
+}
+
+# *type struct? handle? with size
+# maybe should be handled by getbyvalue
+type /^u64:\$\{(\w+)\}$/ select=array-size copy=<pointer> {
+ type {{ "$data->{$m->{type}}->{rename}" }}
+ length {{ 'get'.($m->{'array-size'}->{rename}).'()' }}
+ tojava {{ "$data->{$m->{type}}->{rename}.createArray({value}, {length}, {scope})" }}
+}
+
+# *type struct? handle?
+type /^u64:\$\{(\w+)\}$/ copy=<pointer> {
+ type {{ "$data->{$m->{type}}->{rename}" }}
+ tojava {{ "$data->{$m->{type}}->{rename}.create({value}, {scope})" }}
+}
+
+# *void with size
+type /^u64:v$/ select=array-size copy=<pointer> {
+ type {{ 'MemorySegment' }}
+ length {{ 'get'.($m->{'array-size'}->{rename}).'()' }}
+ tojava {{ "MemorySegment.ofAddress({value}, {length}, {scope})" }}
+}
+
+# *void
+type /^u64:v$/ copy=<pointer> {
+}
+
+# primitive
+type /^(?<ctype>[uif]\d+)$/ copy=<common> {
+ layout {{ "Memory.".uc($typeSizes{$match->{ctype}}) }}
+ carrier {{ "$typeSizes{$match->{ctype}}" }}
+ type {{ "$typeSizes{$match->{ctype}}" }}
+ tonative {{ '({type})({value})' }}
+ tojava {{ '({type})({value})' }}
+}
+
+# type inline
+type /^\$\{(\w+)\}$/ byvalue template=code:getbyvalue {
+ layout {{ '{type}.LAYOUT' }}
+ carrier {{ 'MemorySegment' }}
+ type {{ "$data->{$m->{type}}->{rename}" }}
+ tojava {{ '{type}.create({value})' }}
+ tonative {{ '({value}).segment()' }}
+ getnative {{ '{type}.create((MemorySegment){name}$SH.invokeExact({segment}))' }}
+ setnative;
+ varhandle {{ 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n" }}
+}
+
+type <common> template=code:getset {
+ getnative {{ '({carrier}){name}$VH.get({segment})' }}
+ setnative {{ '{name}$VH.set({segment}, {tonative})' }}
+ varhandle {{ 'final static VarHandle {name}$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n" }}
+ tojava {{ '{value}' }}
+ tonative {{ '{value}' }}
+}
+
+type <pointer> copy=<common> {
+ layout {{ 'Memory.POINTER' }}
+ carrier {{ 'MemoryAddress' }}
+ type {{ 'MemoryAddress' }}
+ tonative {{ '(Addressable)Memory.address({value})' }}
+}
--- /dev/null
+/*
+ * export c types and prototypes to perl file.
+ *
+ * Copyright (c) 2019 Yonatan Goldschmidt
+ * Copyright (c) 2020,2021 Michael Zucchi
+ *
+ * The MIT License (MIT)
+ *
+ * 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.
+ */
+
+/*
+ skeleton taken from structsizes.cc plugin by Richard W.M. Jones
+ https://rwmj.wordpress.com/2016/02/24/playing-with-gcc-plugins/
+ some other bits from
+ https://blog.adacore.com/bindings-gcc-plugins
+ */
+
+/*
+ TODO: get header name from tree
+ */
+
+/*
+
+function declarations, i think
+
+ chain param type name size
+TYPE_DECL TYPE_ARG_TYPES TYPE_VALUE(item):TREE_LIST <empty> TYPE_SIZE(item_type)
+ /POINTER
+ /FUNCTION_TYPE
+FUNCTION_DECL DECL_ARGUENTS TREE_TYPE(item):PARM_DECL DECL_NAME(item) DECL_SIZE(item)
+ TYPE_SIZE(item_type)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <gcc-plugin.h>
+#include <tree.h>
+#include <print-tree.h>
+#include <tree-pretty-print.h>
+
+#include "list.h"
+
+#define D(x) do { x; } while(0)
+
+extern const char *tree_codes[];
+#define ZTREE_CODE(x) tree_codes[TREE_CODE(x)]
+
+//Enums have a type, otherwise they're just integers
+//#define TYPED_ENUMS
+
+// remove some of the debug
+//#define NDEBUG
+
+int plugin_is_GPL_compatible; // must be defined for the plugin to run
+
+static FILE *output_file;
+static char *output_path; // name
+static char *output_tmp; // name~
+static int output_enabled = 1;
+
+static int debug_level = 0;
+
+static void debug_tree_helper(tree t, const char *msg) {
+#ifndef NDEBUG
+ if (debug_level > 2) {
+ fflush(stdout);
+ fprintf(stderr, "dumping tree: '%s'\n", msg);
+ debug_tree(t);
+ fprintf(stderr, "\n\n");
+ fflush(stdout);
+ }
+#endif
+}
+
+static struct hash dumped;
+static struct list todump;
+static struct list context_stack;
+static struct list parameters; // last list of params
+
+static struct hash forward_types;
+
+static int generate(const char *fmt, ...) {
+ int res = 0;
+ if (output_enabled) {
+ va_list ap;
+ va_start(ap, fmt);
+ res = vfprintf(output_file, fmt, ap);
+ va_end(ap);
+ }
+ return res;
+}
+
+/*
+ Join all names in the stack, in reverse order.
+*/
+static char *stack_path(struct list *stack, const char *sep) {
+ size_t total = 1;
+
+ for (struct node *n = stack->tail; n; n=n->link)
+ total += strlen(n->name)+strlen(sep);
+
+ char *data = (char *)xmalloc(total);
+ char *p = data;
+
+ // FIXME: some other context name
+ for (struct node *n = stack->tail; n; n=n->link) {
+ p = stpcpy(p, n->name);
+ if (n->link)
+ p = stpcpy(p, sep);
+ }
+
+ return data;
+}
+
+static void list_clear(struct list *list) {
+ struct node *node;
+
+ while ((node = list_remhead(list))) {
+ if (debug_level > 0)
+ fprintf(stderr, " free: %s\n", node->name);
+ free(node);
+ }
+}
+
+static void todump_add(tree type, const char *name) {
+ struct node *node = node_alloc(type, name);
+
+ list_add(&todump, node);
+}
+
+static void todump_addhead(tree type, const char *name) {
+ struct node *node = node_alloc(type, name);
+
+ list_addhead(&todump, node);
+}
+
+// returns 0 if type has no size (i.e VOID_TYPE)
+static bool is_struct_or_union(const_tree type) {
+ return RECORD_TYPE == TREE_CODE(type) || UNION_TYPE == TREE_CODE(type);
+}
+
+static void print_spaces(int n) {
+ if (output_enabled) {
+ for (int i = 0; i < n; ++i)
+ fputc('\t', output_file);
+ }
+}
+
+static int is_ref_type(tree type) {
+ switch (TREE_CODE(type)) {
+ case VECTOR_TYPE:
+ case ARRAY_TYPE:
+ case POINTER_TYPE:
+ case REFERENCE_TYPE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static tree target_type(tree type) {
+ while (is_ref_type(type))
+ type = TREE_TYPE(type);
+ return type;
+}
+
+static size_t value_size(tree n) {
+ return n ? tree_to_uhwi(n) : 0;
+}
+
+static const char *value_name(tree type) {
+ tree test = TYPE_IDENTIFIER(type);
+ const char *value = test ? IDENTIFIER_POINTER(test) : NULL;
+
+ // __va_list_tag is the final type beneath __builtin_va_list.
+ // it behaves different from other types - it has a TYPE_MAIN_VARIANT, but the main TYPE_NAME seems to give
+ // an unexpected tree, and therefore ORIG_TYPE_NAME returns a garbage value.
+ // I think this patch is good enough.
+ if (value && 0 == strcmp("__va_list_tag", value))
+ return "__va_list_tag";
+
+ test = TYPE_MAIN_VARIANT(type);
+ test = test ? TYPE_NAME(test) : test;
+ test = test && TREE_CODE(test) == TYPE_DECL ? DECL_NAME(test) : test;
+
+ return test ? IDENTIFIER_POINTER(test) : value;
+}
+
+/*
+ Find a non-ref type in the type chain, i.e. skip pointers/arrays.
+*/
+static tree simple_type(tree t) {
+ while (is_ref_type(t))
+ t = TREE_TYPE(t);
+ return t;
+}
+
+/*
+ Create a 'panama' signature for a single type.
+*/
+static void export_desc(tree field, tree field_type, struct buffer *b) {
+ const size_t data_size = value_size(TYPE_SIZE(field_type));
+
+ switch (TREE_CODE(field_type)) {
+ case VOID_TYPE:
+ buffer_add(b, "v");
+ break;
+ case VECTOR_TYPE:
+ case ARRAY_TYPE: {
+ const size_t elem_size = tree_to_uhwi(TYPE_SIZE_UNIT(TREE_TYPE(field_type)));
+ size_t num_elem;
+
+ if (elem_size == 0 || NULL == TYPE_SIZE_UNIT(field_type)) {
+ // it is a flexible array or empty types
+ num_elem = 0;
+ } else {
+ // it might be 0 / elem_size, in which case we also end up with num_elem = 0.
+ num_elem = tree_to_uhwi(TYPE_SIZE_UNIT(field_type)) / elem_size;
+ }
+
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "[%zu", num_elem);
+ export_desc(field, TREE_TYPE(field_type), b);
+ buffer_add(b, "]");
+ break;
+ }
+ case POINTER_TYPE:
+ case REFERENCE_TYPE:
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "u%zu:", data_size);
+ export_desc(field, TREE_TYPE(field_type), b);
+ break;
+ case FUNCTION_TYPE: {
+ // TODO: handle void f() type -> null TYPE_ARG_TYPES()
+ tree return_type = TREE_TYPE(field_type);
+
+ buffer_add(b, "(");
+ for (tree param = TYPE_ARG_TYPES(field_type); param != NULL; param = TREE_CHAIN(param)) {
+ tree param_type = TREE_VALUE(param);
+
+ if (TREE_CODE(param_type) == VOID_TYPE)
+ break;
+
+ export_desc(param, param_type, b);
+ }
+ buffer_add(b, ")");
+ export_desc(field, return_type, b);
+ break;
+ }
+ case ENUMERAL_TYPE:
+#if defined(TYPED_ENUMS)
+ buffer_add(b, "${");
+ buffer_add(b, TYPE_IDENTIFIER(field_type) ? value_name(field_type) : "enum");
+ buffer_add(b, "}");
+#else
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "%c%zu",
+ TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+ data_size);
+#endif
+ break;
+ case RECORD_TYPE:
+ case UNION_TYPE:
+ if (TYPE_IDENTIFIER(field_type)) {
+ todump_add(field_type, NULL);
+
+ buffer_add(b, "${");
+ buffer_add(b, value_name(field_type));
+ buffer_add(b, "}");
+ } else {
+ char *name = stack_path(&context_stack, "_");
+
+ todump_add(field_type, name);
+
+ buffer_add(b, "${");
+ buffer_add(b, name);
+ buffer_add(b, "}");
+
+ free(name);
+ }
+ break;
+ case REAL_TYPE:
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "f%zu", data_size);
+ break;
+ case INTEGER_TYPE:
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "%c%zu",
+ TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+ data_size);
+ break;
+ default:
+ debug_tree_helper(field_type, "unknown type!");
+ gcc_unreachable();
+ break;
+ }
+}
+
+/*
+ Create a 'c' description of type.
+*/
+static void export_cdesc(tree field, tree field_type, struct buffer *b) {
+ const size_t data_size = value_size(TYPE_SIZE(field_type));
+
+ switch (TREE_CODE(field_type)) {
+ case VOID_TYPE:
+ buffer_add(b, "void");
+ break;
+ case VECTOR_TYPE:
+ case ARRAY_TYPE: {
+ const size_t elem_size = tree_to_uhwi(TYPE_SIZE_UNIT(TREE_TYPE(field_type)));
+ size_t num_elem;
+
+ if (elem_size == 0 || NULL == TYPE_SIZE_UNIT(field_type)) {
+ // it is a flexible array or empty types
+ num_elem = 0;
+ } else {
+ // it might be 0 / elem_size, in which case we also end up with num_elem = 0.
+ num_elem = tree_to_uhwi(TYPE_SIZE_UNIT(field_type)) / elem_size;
+ }
+
+ export_cdesc(field, TREE_TYPE(field_type), b);
+
+ buffer_room(b, 16);
+ buffer_add(b, "[");
+ if (num_elem)
+ b->pos += sprintf(b->data + b->pos, "%zu", num_elem);
+ buffer_add(b, "]");
+ break;
+ }
+ case POINTER_TYPE:
+ case REFERENCE_TYPE:
+ buffer_room(b, 16);
+ buffer_add(b, "*");
+ //b->pos += sprintf(b->data + b->pos, "u%zu:", data_size);
+ export_cdesc(field, TREE_TYPE(field_type), b);
+ break;
+ case FUNCTION_TYPE: {
+ // TODO: handle void f() type -> null TYPE_ARG_TYPES()
+ tree return_type = TREE_TYPE(field_type);
+ int args = 0;
+
+ export_cdesc(field, return_type, b);
+
+ buffer_add(b, "(*)");
+ buffer_add(b, "(");
+ for (tree param = TYPE_ARG_TYPES(field_type); param != NULL; param = TREE_CHAIN(param)) {
+ tree param_type = TREE_VALUE(param);
+
+ // TREE_TYPE might? point to a type that a previous decl has also referenced
+
+ if (args++)
+ buffer_add(b, ", ");
+
+ if (TREE_CODE(param_type) == VOID_TYPE) {
+ buffer_add(b, "void");
+ break;
+ }
+
+ export_cdesc(param, param_type, b);
+ }
+ buffer_add(b, ")");
+ break;
+ }
+ case ENUMERAL_TYPE:
+#if defined(TYPED_ENUMS)
+ buffer_add(b, "${");
+ buffer_add(b, TYPE_IDENTIFIER(field_type) ? value_name(field_type) : "enum");
+ buffer_add(b, "}");
+#else
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "%c%zu",
+ TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+ data_size);
+#endif
+ break;
+ case RECORD_TYPE:
+ case UNION_TYPE:
+ if (TYPE_IDENTIFIER(field_type)) {
+ todump_add(field_type, NULL);
+
+ buffer_add(b, "${");
+ buffer_add(b, value_name(field_type));
+ buffer_add(b, "}");
+ } else {
+ char *name = stack_path(&context_stack, "_");
+
+ todump_add(field_type, name);
+
+ buffer_add(b, "${");
+ buffer_add(b, name);
+ buffer_add(b, "}");
+
+ free(name);
+ }
+ break;
+ case REAL_TYPE:
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "f%zu", data_size);
+ break;
+ case INTEGER_TYPE:
+ buffer_room(b, 16);
+ b->pos += sprintf(b->data + b->pos, "%c%zu",
+ TYPE_UNSIGNED(field_type) ? 'u' : 'i',
+ data_size);
+ break;
+ default:
+ debug_tree_helper(field_type, "unknown type!");
+ gcc_unreachable();
+ break;
+ }
+}
+
+/*
+ Print a single parameter or field.
+*/
+static void export_param(tree field, tree field_type, size_t field_size) {
+ static tree enclosing_type = NULL;
+
+ if (debug_level > 1) {
+ fprintf(stderr, "export_param (%s, %s)\n", ZTREE_CODE(field), ZTREE_CODE(field_type));
+ //fprintf(stderr, " field name '%s'\n", TYPE_IDENTIFIER(field) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(field)) : "<unknown>");
+ fprintf(stderr, " field_type name '%s'\n", TYPE_IDENTIFIER(field_type) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(field_type)) : "<unknown>");
+ debug_tree(field_type);
+ }
+
+ switch (TREE_CODE(field_type)) {
+ case VECTOR_TYPE:
+ case ARRAY_TYPE:
+ case POINTER_TYPE:
+ case REFERENCE_TYPE: {
+ tree base_type;
+ size_t base_size;
+ struct buffer b;
+
+ buffer_init(&b, 256);
+ export_desc(field, field_type, &b);
+ generate(" deref => '%s',", b.data);
+ free(b.data);
+
+ base_type = simple_type(field_type);
+ base_size = value_size(TYPE_SIZE(base_type));
+
+ if (debug_level > 1)
+ fprintf(stderr, "recurse %s %s\n", ZTREE_CODE(field_type), ZTREE_CODE(base_type));
+
+ enclosing_type = field_type;
+ export_param(field, base_type, base_size);
+ enclosing_type = NULL;
+ break;
+ }
+ case VOID_TYPE:
+ 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";
+ generate(" type => 'enum:%s',", names);
+#else
+ generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size);
+#endif
+ generate(" ctype => 'enum %s',", value_name(field_type));
+ break;
+ }
+ case FUNCTION_TYPE: {
+ struct buffer b;
+ tree root_type = TREE_TYPE(field);
+
+ if (root_type == NULL || !TYPE_IDENTIFIER(root_type))
+ root_type = enclosing_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)) {
+ generate(" type => 'call:%s', ", value_name(root_type));
+ } else {
+ // note it is added to todump in reverse
+ buffer_init(&b, 256);
+ export_desc(field, field_type, &b);
+ todump_addhead(field_type, b.data);
+ generate(" type => 'call:%s', ", b.data);
+ if (debug_level > 0) {
+ fprintf(stderr, "save for later param type %p root type %p '%s'\n", field_type, root_type, b.data);
+
+ if (root_type) {
+ fprintf(stderr, "type is '%s\n", ZTREE_CODE(root_type));
+ fprintf(stderr, "type name is '%s'\n", TYPE_IDENTIFIER(root_type) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(root_type)) : "<anon>");
+ fprintf(stderr, "target is '%s'\n", ZTREE_CODE(target_type(root_type)));
+ }
+ }
+ }
+
+ //buffer_init(&b, 256);
+ //export_cdesc(field, field_type, &b);
+ //free(b.data);
+
+ break;
+ }
+ case REAL_TYPE:
+ 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)) {
+ generate(" ctype => 'bitfield',");
+ generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', value_size(DECL_SIZE(field)));
+ } else {
+ generate(" ctype => '%s',", value_name(field_type));
+ generate(" type => '%c%zu',", TYPE_UNSIGNED(field_type) ? 'u' : 'i', field_size);
+ }
+ break;
+ case RECORD_TYPE:
+ case UNION_TYPE: {
+ const char *us = TREE_CODE(field_type) == RECORD_TYPE ? "struct" : "union";
+
+ if (TYPE_IDENTIFIER(field_type)) {
+ generate(" type => '%s:%s',", us, value_name(field_type));
+ } else {
+ char *name = stack_path(&context_stack, "_");
+
+ todump_add(field_type, name);
+ generate(" type => '%s:%s',", us, name);
+ free(name);
+ }
+ break;
+ }
+ default:
+ fprintf(stderr, "unknown param type: %s\n", ZTREE_CODE(field_type));
+ gcc_unreachable();
+ }
+}
+
+/*
+ Export a chain of parameters
+*/
+static void export_params(tree func) {
+ int id = 0;
+ char nameb[32];
+ struct list args = { 0 };
+ struct node *name;
+ struct node *fwd = hash_lookup_bytype(&forward_types, func);
+
+ if (fwd) {
+ // use the forward reference to find the names
+ if (debug_level > 0)
+ fprintf(stderr, "found forward reference @ %p\n", fwd->type);
+
+ // so sometimes at this point the parameters stack has items we should
+ // remove ... but not always. apart from leaving garbage on the
+ // stack it doesn't seem to upset the output
+
+ name = fwd->list.head;
+ } else {
+ // paramter names are in the paramters list
+ // but they are in reverse order
+ int id = 0;
+ for (tree param = TYPE_ARG_TYPES(func); param; param = TREE_CHAIN(param)) {
+ tree param_type = TREE_VALUE(param);
+
+ if (!TREE_CHAIN(param) && TREE_CODE(param_type) == VOID_TYPE) {
+ if (id == 0) {
+ struct node *decl = stack_pull(¶meters);
+ if (debug_level > 0)
+ fprintf(stderr, "(pull parameter dummy '%s')\n", decl->name);
+ }
+ break;
+ }
+
+ struct node *decl = stack_pull(¶meters);
+ if (decl) {
+ if (debug_level > 0)
+ fprintf(stderr, "(pull parameter args '%s')\n", decl->name);
+ stack_push(&args, decl);
+ } else
+ fprintf(stderr, "ERROR: parameter %d missing parameter declaration\n", id);
+ id++;
+ }
+ name = args.head;
+ }
+
+ for (tree param = TYPE_ARG_TYPES(func); param; param = TREE_CHAIN(param)) {
+ tree param_type = TREE_VALUE(param);
+ const size_t data_size = value_size(TYPE_SIZE(param_type));
+ const char *names = NULL;
+
+ // non-varags functions end in VOID_TYPE
+ if (!TREE_CHAIN(param) && TREE_CODE(param_type) == VOID_TYPE)
+ break;
+
+ generate("\t\t{");
+
+ // size: do we need it?
+ generate(" size => %zu,", data_size);
+
+ if (name) {
+ // this should be a parm_decl with an identifier of the name
+ names = name->name;
+
+ // can one check it's a matching type?
+ name = name->next;
+ }
+
+ if (!names || !names[0]) {
+ sprintf(nameb, "arg$%d", id);
+ names = nameb;
+ }
+
+ generate(" name => '%s',", names);
+ stack_push(&context_stack, node_alloc(param, names));
+
+ // value: details
+ export_param(param, param_type, data_size);
+
+ free(stack_pull(&context_stack));
+
+ generate("},\n");
+ id++;
+ }
+
+ list_clear(&args);
+}
+
+/*
+ Export a chain of fields.
+*/
+static void export_fields(tree first_field, size_t base_offset, int indent) {
+ for (tree field = first_field; field; field = TREE_CHAIN(field)) {
+ gcc_assert(TREE_CODE(field) == FIELD_DECL);
+
+ tree field_type = TREE_TYPE(field);
+ const size_t field_size = value_size(DECL_SIZE(field));
+ size_t offset = base_offset + tree_to_uhwi(DECL_FIELD_OFFSET(field)) * 8 + tree_to_uhwi(DECL_FIELD_BIT_OFFSET(field));
+
+ // name: if none, then inline
+ if (!DECL_NAME(field)) {
+ if (is_struct_or_union(field_type))
+ export_fields(TYPE_FIELDS(field_type), offset, indent);
+ } else {
+ const char *names = IDENTIFIER_POINTER(DECL_NAME(field));
+
+ if (debug_level > 1)
+ fprintf(stderr, " field: %s\n", names);
+ print_spaces(indent+1);
+ generate("{ name => '%s', size => %zu, offset => %zu,", names, field_size, offset);
+ stack_push(&context_stack, node_alloc(field, names));
+
+ // value: details
+ export_param(field, field_type, field_size);
+
+ free(stack_pull(&context_stack));
+
+ generate("},\n");
+ }
+ }
+}
+
+// try to find a filename for this node
+// it isn't really good enough for filtering, e.g. enums have no name
+static const char *source_filename(tree type) {
+ if (DECL_P(type)) {
+ expanded_location xloc = expand_location(DECL_SOURCE_LOCATION(type));
+ return xloc.file;
+ } else if (is_struct_or_union(type)) {
+ for (tree field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) {
+ return source_filename((field));
+ }
+ }
+ // shrug
+ return "<unknown>";
+}
+
+/*
+ Main entry point for exporting any type.
+*/
+static void export_type(tree type, const char *names) {
+ tree name;
+ tree deftype;
+ tree target;
+
+ switch (TREE_CODE(type)) {
+ case TYPE_DECL: {
+ if (!names) {
+ name = DECL_NAME(type);
+ if (!name)
+ return;
+ names = IDENTIFIER_POINTER(name);
+ }
+
+ deftype = TREE_TYPE(type);
+ target = target_type(deftype);
+
+ if (debug_level > 1)
+ fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
+
+ switch (TREE_CODE(target)) {
+ case FUNCTION_TYPE: {
+ // TODO: should be able to call export_type(target, names) here, except code is slightly different
+
+ if (hash_lookup(&dumped, names)) {
+ if (debug_level > 1)
+ fprintf(stderr, " - type already output, skipping");
+ return;
+ }
+ hash_put(&dumped, node_alloc(type, names));
+
+ // function pointer typdef
+ // I don't know if i even want this
+ generate("'call:%s' => { name => '%s', type => 'call',", names, names);
+
+ // the deftype is always a pointer for a function_type
+
+ struct buffer b;
+
+ buffer_init(&b, 256);
+ export_desc(type, deftype, &b);
+ generate(" deref => '%s',", b.data);
+ free(b.data);
+
+ 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));
+
+ generate("\n\tresult => {");
+ export_param(target, result_type, data_size);
+ generate(" },");
+ }
+
+ generate("\n\targuments => [\n");
+ export_params(target);
+ generate("]},\n");
+ break;
+ }
+ case ENUMERAL_TYPE: {
+ if (hash_lookup(&dumped, names)) {
+ if (debug_level > 1)
+ fprintf(stderr, " - type already output, skipping");
+ return;
+ }
+ hash_put(&dumped, node_alloc(type, names));
+
+ // TODO: should be able to call export_type(target, names) here
+ // TODO: typedef of anonymous enumerations may be repeated
+ // TODO: this could be detected in the frontend - e.g. don't include any
+ // TODO: anon enum values if any are already there
+ // TODO: or maybe duplicates could be detected here
+ size_t size = tree_to_uhwi(TYPE_SIZE(target));
+
+ if (size > 64) {
+ fprintf(stderr, "Warning: enum '%s' requires too many bits (%zu)\n", names, size);
+ return;
+ }
+
+ 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)) {
+ generate("\t{ name => '%s', value => '%ld' },\n",
+ IDENTIFIER_POINTER(TREE_PURPOSE(v)),
+ tree_to_shwi(TREE_VALUE(v)));
+ }
+ generate("]},\n");
+ break;
+ }
+ case RECORD_TYPE:
+ case UNION_TYPE:
+ if (TYPE_FIELDS(target)) {
+ // 'typedef struct { } foo;'
+ export_type(target, names);
+ } else {
+ // forward declaration or opaque types
+ if (debug_level)
+ fprintf(stderr, "forward ignored %s: %s\n", ZTREE_CODE(target), names);
+ }
+ break;
+ case VOID_TYPE:
+ case INTEGER_TYPE:
+ case REAL_TYPE:
+ if (debug_level)
+ fprintf(stderr, "ignored %s: %s\n", ZTREE_CODE(target), names);
+ break;
+ default:
+ fprintf(stderr, "unknown type def: %s\n", ZTREE_CODE(target));
+ gcc_unreachable();
+ }
+
+ break;
+ }
+ case FUNCTION_DECL: {
+ if (!names) {
+ name = DECL_NAME(type);
+ if (!name)
+ return;
+ names = IDENTIFIER_POINTER(name);
+ }
+ if (hash_lookup(&dumped, names))
+ return;
+ hash_put(&dumped, node_alloc(type, names));
+
+ if (debug_level > 1)
+ fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
+
+ generate("'func:%s' => { name => '%s', type => 'func',", names, names);
+
+ // FUNCTION_DECL -> FUNCTION_TYPE -> RESULT_TYPE, get FUNCTION_TYPE
+ type = TREE_TYPE(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));
+
+ generate("\n\tresult => {");
+ export_param(type, result_type, data_size);
+ generate(" },");
+ }
+
+ generate("\n\targuments => [\n");
+ //export_decl_params(DECL_ARGUMENTS(type), 0);
+ export_params(type);
+ generate("]},\n");
+ break;
+ }
+ case FUNCTION_TYPE: {
+ int output_save = output_enabled;
+
+ // This is called for un-typedef'd function pointers.
+ // WHY IS THIS DIFFERENT TO ABOVE?
+ if (!names) {
+ name = TYPE_IDENTIFIER(type);
+ if (!name)
+ return;
+ names = IDENTIFIER_POINTER(name);
+ }
+ if (hash_lookup(&dumped, names)) {
+ if (debug_level > 0)
+ fprintf(stderr, " - type already output, supressing generation\n");
+ output_enabled = 0;
+ }
+ hash_put(&dumped, node_alloc(type, names));
+
+ if (debug_level > 1)
+ fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
+
+ generate("'call:%s' => { name => '%s', type => 'call',", names, names);
+ generate(" ctype => '%s',", print_generic_expr_to_str(type));
+
+ debug_tree_helper(type, "function type");
+
+ // TODO: cleanup
+ // FUNCTION_TYPE -> RESULT_TYPE
+ {
+ tree result = TREE_TYPE(type);
+ //tree result_type = TREE_TYPE(result);
+ //printf(" result type type %s\n", ZTREE_CODE(result_type));
+ const size_t data_size = value_size(TYPE_SIZE(result));
+
+ if (debug_level > 2)
+ fprintf(stderr, " result size %zu\n", data_size);
+ generate("\n\tresult => {");
+ export_param(type, result, data_size);
+ generate(" },");
+ }
+
+ stack_push(&context_stack, node_alloc(type, names));
+ generate("\n\targuments => [\n");
+ export_params(type);
+ free(stack_pull(&context_stack));
+ generate("]},\n");
+
+ output_enabled = output_save;
+ break;
+ }
+ case RECORD_TYPE: // struct
+ case UNION_TYPE: {
+ const char *su = TREE_CODE(type) == RECORD_TYPE ? "struct" : "union";
+
+ // ignore empty types
+ if (!TYPE_FIELDS(type))
+ return;
+
+ if (!names) {
+ name = TYPE_IDENTIFIER(type);
+ if (!name)
+ return;
+ names = IDENTIFIER_POINTER(name);
+ }
+ if (hash_lookup(&dumped, names)) {
+ if (debug_level > 1)
+ fprintf(stderr, "already exported: %-16s %s\n", ZTREE_CODE(type), names);
+ return;
+ }
+ hash_put(&dumped, node_alloc(type, names));
+
+ if (debug_level > 1)
+ fprintf(stderr, "export: %-16s %s\n", ZTREE_CODE(type), names);
+
+ generate("'%s:%s' => { name => '%s', type => '%s', size => %zu, fields => [\n",
+ su, names, names, su, tree_to_uhwi(TYPE_SIZE(type)));
+
+ stack_push(&context_stack, node_alloc(type, names));
+ export_fields(TYPE_FIELDS(type), 0, 0);
+ free(stack_pull(&context_stack));
+
+ generate("]},\n");
+ break;
+ }
+ case ENUMERAL_TYPE: {
+ // FIXME: see ENUMERAL_TYPE above regarding duplicate anon enums
+
+ // ignore empty enums
+ if (!TYPE_VALUES(type))
+ return;
+
+ // We can only assume a non-named enum type isn't repeatedly sent here
+ if (!names) {
+ name = TYPE_IDENTIFIER(type);
+ if (name)
+ names = IDENTIFIER_POINTER(name);
+ }
+ if (names) {
+ if (hash_lookup(&dumped, names))
+ return;
+ hash_put(&dumped, node_alloc(type, names));
+ }
+
+ size_t size = tree_to_uhwi(TYPE_SIZE(type));
+
+ if (size > 64) {
+ fprintf(stderr, "Warning: enum '%s' requires too many bits (%zu)\n", names, size);
+ return;
+ }
+
+ // FIXME: choose a better anon name
+ char nameb[64];
+ static int namei;
+
+ if (!names) {
+ sprintf(nameb, "enum$%d", namei++);
+ names = nameb;
+ }
+
+ if (debug_level > 1)
+ fprintf(stderr, "export: %s %s\n", names, ZTREE_CODE(type));
+
+ 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)) {
+ generate("\t{ name => '%s', value => '%ld' },\n",
+ IDENTIFIER_POINTER(TREE_PURPOSE(v)),
+ tree_to_shwi(TREE_VALUE(v)));
+ }
+ generate("]},\n");
+ break;
+ }
+ case FIELD_DECL: {
+ if (!names) {
+ name = DECL_NAME(type);
+ if (name)
+ names = IDENTIFIER_POINTER(name);
+ }
+
+ deftype = TREE_TYPE(type);
+ target = target_type(deftype);
+
+ if (debug_level > 1) {
+ fprintf(stderr, "field: %-16s %s %s -> %s\n", ZTREE_CODE(deftype), names,
+ TYPE_IDENTIFIER(deftype) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(deftype)) : "<anon>",
+ ZTREE_CODE(target));
+ }
+
+ // nb almost same as next case below, but we don't save the param info here
+ if (TREE_CODE(target) == FUNCTION_TYPE && !TYPE_IDENTIFIER(deftype)) {
+ // We need to save the list of parameters for later,
+ // it's keyed on target
+ struct node *fwd = node_alloc(target, NULL);
+
+ if (debug_level > 0) {
+ struct buffer b;
+
+ buffer_init(&b, 256);
+ export_desc(deftype, target, &b);
+
+ fprintf(stderr, "save forward reference field function type %p '%s' '%s'\n", target, names, b.data);
+ // or should be todump?
+ free(b.data);
+ }
+
+ for (tree param = TYPE_ARG_TYPES(target); param != NULL; param = TREE_CHAIN(param)) {
+ tree param_type = TREE_VALUE(param);
+
+ if (TREE_CODE(param_type) == VOID_TYPE)
+ break;
+
+ struct node *decl = stack_pull(¶meters);
+ if (decl) {
+ if (debug_level > 0)
+ fprintf(stderr, "(pull parameter '%s')\n", decl->name);
+ stack_push(&fwd->list, decl);
+ } else
+ fprintf(stderr, "WARNING: stack is missing parameter name function %s\n", names);
+ }
+
+ hash_put_bytype(&forward_types, fwd);
+
+ // make sure it's dumped somewhere
+ // what if it's a typedef?
+
+ // ... we don't know if'ts a typedef though right?
+ // note it is added to todump in reverse
+ if (0) {
+ struct buffer b;
+
+ buffer_init(&b, 256);
+ export_desc(deftype, target, &b);
+ todump_addhead(target, b.data);
+
+ if (debug_level > 0)
+ fprintf(stderr, "save for later param type %p '%s'\n", target, b.data);
+
+ free(b.data);
+ }
+ }
+
+ break;
+ }
+ case PARM_DECL: {
+ // capture PARM_DECLs so they can be used at next function declaration
+ if (!names) {
+ name = DECL_NAME(type);
+ if (name)
+ names = IDENTIFIER_POINTER(name);
+ }
+ // if this is a function pointer typedef, need to suck out the arguments at this point
+ deftype = TREE_TYPE(type);
+ target = target_type(deftype);
+
+ if (debug_level > 1) {
+ fprintf(stderr, "type is '%s\n", ZTREE_CODE(deftype));
+ fprintf(stderr, "type name is '%s'\n", TYPE_IDENTIFIER(deftype) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(deftype)) : "<anon>");
+ fprintf(stderr, "target is '%s'\n", ZTREE_CODE(target));
+ }
+
+ if (TREE_CODE(target) == FUNCTION_TYPE && !TYPE_IDENTIFIER(deftype)) {
+ // We need to save the list of parameters for later,
+ // it's keyed on target
+ struct node *fwd = node_alloc(target, NULL);
+
+ if (debug_level > 0) {
+ struct buffer b;
+
+ buffer_init(&b, 256);
+ export_desc(deftype, target, &b);
+
+ fprintf(stderr, "save forward reference function type %p '%s' '%s'\n", target, names, b.data);
+ free(b.data);
+ }
+
+ for (tree param = TYPE_ARG_TYPES(target); param != NULL; param = TREE_CHAIN(param)) {
+ tree param_type = TREE_VALUE(param);
+
+ if (TREE_CODE(param_type) == VOID_TYPE)
+ break;
+
+ struct node *decl = stack_pull(¶meters);
+ if (decl) {
+ if (debug_level > 0)
+ fprintf(stderr, "(pull parameter '%s')\n", decl->name);
+ stack_push(&fwd->list, decl);
+ } else
+ fprintf(stderr, "WARNING: stack is missing parameter name function %s\n", names);
+ }
+
+ hash_put_bytype(&forward_types, fwd);
+
+ // make sure it's dumped somewhere
+ // what if it's a typedef?
+
+ // ... we don't know if'ts a typedef though right?
+ // note it is added to todump in reverse
+ if (1) {
+ struct buffer b;
+
+ buffer_init(&b, 256);
+ export_desc(deftype, target, &b);
+ todump_addhead(target, b.data);
+
+ if (debug_level > 0)
+ fprintf(stderr, "save for later param type %p '%s'\n", target, b.data);
+
+ free(b.data);
+ }
+
+ }
+
+ if (debug_level > 0)
+ fprintf(stderr, "(push parameter '%s' %s)\n", names, source_filename(type));
+ stack_push(¶meters, node_alloc(type, names));
+
+ break; }
+ case VAR_DECL:
+ // global external variables, might want these
+ // well, if there's a way to resolve them
+ break;
+ default:
+ fprintf(stderr, "unknown export: %s\n", ZTREE_CODE(type));
+ gcc_unreachable();
+ }
+}
+
+static void plugin_finish_type(void *event_data, void *user_data) {
+ tree type = (tree)event_data;
+
+ if (debug_level > 0)
+ fprintf(stderr, "finish_type\n");
+ if (debug_level > 1)
+ debug_tree(type);
+ if (debug_level > 2)
+ dump_node(type, TDF_ALL_VALUES, stderr);
+
+ export_type(type, NULL);
+}
+
+static void plugin_finish_decl(void *event_data, void *user_data) {
+ tree type = (tree)event_data;
+
+ if (debug_level > 0)
+ fprintf(stderr, "finish_decl %s\n", ZTREE_CODE(type));
+ if (debug_level > 1)
+ debug_tree(type);
+ if (debug_level > 2)
+ dump_node(type, TDF_ALL_VALUES, stderr);
+
+ export_type(type, NULL);
+}
+
+static void plugin_finish(void *event_data, void *user_data) {
+ if (debug_level > 0)
+ fprintf(stderr, "\n\n----------------------------------------\nplugin finish\n");
+ generate("# forward references:\n");
+ for (struct node *n = todump.head; n; n=n->next) {
+ if (COMPLETE_TYPE_P(n->type)) {
+ if (n->name[0]) {
+ export_type(n->type, n->name);
+ } else {
+ export_type(n->type, value_name(n->type));
+ }
+ }
+ }
+
+ generate("# dumped structs:\n");
+ for (struct node *n = dumped.list.head; n; n=n->next)
+ generate("# %s\n", n->name);
+
+ generate("}\n");
+
+ if (debug_level > 0)
+ fprintf(stderr, "unhandled paramters:\n");
+ list_clear(¶meters);
+
+ if (fclose(output_file) != 0
+ || rename(output_tmp, output_path) != 0) {
+ perror(output_tmp);
+ exit(EXIT_FAILURE);
+ }
+}
+
+int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) {
+ const char *output = NULL;
+
+ for (int i = 0; i < plugin_info->argc; ++i) {
+ if (0 == strcmp(plugin_info->argv[i].key, "output")) {
+ output = plugin_info->argv[i].value;
+ } else if (0 == strcmp(plugin_info->argv[i].key, "debug")) {
+ debug_level = atoi(plugin_info->argv[i].value);
+ }
+ }
+
+ if (NULL == output) {
+ fprintf(stderr, "export plugin: missing parameter: -fplugin-arg-export-output=<output>\n");
+ exit(EXIT_FAILURE);
+ }
+
+ output_path = xstrdup(output);
+ output_tmp = (char *)xmalloc(strlen(output)+2);
+ strcpy(stpcpy(output_tmp, output), "~");
+
+ output_file = fopen(output_tmp, "w");
+ if (NULL == output_file) {
+ perror(output);
+ exit(EXIT_FAILURE);
+ }
+
+ generate("{\n");
+
+ //register_callback(plugin_info->base_name, PLUGIN_INCLUDE_FILE, plugin_include_file, NULL);
+ register_callback(plugin_info->base_name, PLUGIN_FINISH_DECL, plugin_finish_decl, NULL);
+ register_callback(plugin_info->base_name, PLUGIN_FINISH_TYPE, plugin_finish_type, NULL);
+ register_callback(plugin_info->base_name, PLUGIN_FINISH, plugin_finish, NULL);
+
+ return 0;
+}
--- /dev/null
+
+/*
+ Copyright (C) 2010,2019,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/>.
+*/
+
+/* **********************************************************************
+ * a simple ordered hash/list/stack
+ */
+
+struct node;
+
+struct list {
+ struct node *head;
+ struct node *tail;
+};
+
+struct node {
+ // list/stack next
+ struct node *next;
+ // hash chain link/stack prev
+ struct node *link;
+
+ // metadata
+ struct list list;
+
+ // payload
+ tree type;
+
+ char name[];
+};
+
+struct hash {
+ struct node *table[64];
+ struct list list;
+};
+
+static struct node *node_alloc(tree type, const char *name) {
+ struct node *n = (struct node *)xmalloc(sizeof(struct node) + (name ? strlen(name) + 1 : 1));
+
+ n->next = NULL;
+ n->link = NULL;
+ n->type = type;
+ n->list.head = NULL;
+ n->list.tail = NULL;
+
+ if (name)
+ strcpy(n->name, name);
+ else
+ n->name[0] = 0;
+
+ return n;
+}
+
+static void stack_push(struct list *stack, struct node *node) {
+ if (stack->head) {
+ stack->head->link = node;
+ node->next = stack->head;
+ stack->head = node;
+ } else {
+ node->next = NULL;
+ stack->head = node;
+ stack->tail = node;
+ }
+}
+
+static struct node *stack_pull(struct list *stack) {
+ struct node *n = stack->head;
+
+ if (n) {
+ stack->head = n->next;
+ if (stack->head)
+ stack->head->link = NULL;
+ else
+ stack->tail = NULL;
+ }
+
+ return n;
+}
+
+static unsigned int ez_hash_string(const char *s) {
+ unsigned int hash = 5381;
+
+ while (*s)
+ //hash = (*s++) + (hash << 5) + hash;
+ hash = hash * 33 + (*s++);
+
+ return hash;
+}
+
+static void list_add(struct list *list, struct node *node) {
+ node->next = NULL;
+ if (list->head) {
+ list->tail->next = node;
+ list->tail = node;
+ } else {
+ list->head = node;
+ list->tail = node;
+ }
+}
+
+static void list_addhead(struct list *list, struct node *node) {
+ node->next = list->head;
+ list->head = node;
+ if (!list->tail)
+ list->tail = node;
+}
+
+static struct node *list_remhead(struct list *list) {
+ struct node *node = list->head;
+
+ if (node && (list->head = node->next) == NULL)
+ list->tail = NULL;
+
+ return node;
+}
+
+static void hash_put(struct hash *hash, struct node *node) {
+ int code = ez_hash_string(node->name) & 63;
+
+ list_add(&hash->list, node);
+
+ node->link = hash->table[code];
+ hash->table[code] = node;
+}
+
+struct node *hash_lookup(struct hash *hash, const char *name) {
+ int code = ez_hash_string(name) & 63;
+
+ for (struct node *n = hash->table[code]; n; n=n->link) {
+ if (strcmp(n->name, name) == 0)
+ return n;
+ }
+
+ return NULL;
+}
+
+static void hash_put_bytype(struct hash *hash, struct node *node) {
+ int code = (int)((uintptr_t)node->type >> 5) & 63;
+
+ list_add(&hash->list, node);
+
+ node->link = hash->table[code];
+ hash->table[code] = node;
+}
+
+struct node *hash_lookup_bytype(struct hash *hash, tree type) {
+ int code = (int)((uintptr_t)type >> 5) & 63;
+
+ for (struct node *n = hash->table[code]; n; n=n->link) {
+ if (n->type == type)
+ return n;
+ }
+
+ return NULL;
+}
+
+/* simple growable c buffer */
+struct buffer {
+ size_t size;
+ size_t pos;
+ char *data;
+};
+
+static void buffer_init(struct buffer *b, size_t size) {
+ b->size = size;
+ b->pos = 0;
+ b->data = (char *)xmalloc(size);
+}
+
+static void buffer_room(struct buffer *b, size_t len) {
+ if (b->pos + len >= b->size) {
+ do {
+ b->size *= 2;
+ } while (b->pos + len >= b->size);
+
+ b->data = (char *)xrealloc(b->data, b->size);
+ }
+}
+
+static void buffer_add(struct buffer *b, const char *v) {
+ size_t len = strlen(v);
+
+ buffer_room(b, len);
+ strcpy(b->data + b->pos, v);
+ b->pos += len;
+}
--- /dev/null
+
+notzed.nativez_NATIVE_LIBRARIES = export
+
+notzed.nativez_COMMANDS=export-api export-defines generate-api
+notzed.nativez_LIBRARIES=api.pm code.api code.pm config.pm method.pm tokenise.pm types.api
+
+# JMOD target
+$(notzed.nativez_libdir)/%: src/notzed.nativez/lib/%
+ install -D $< $@
+$(notzed.nativez_bindir)/%: src/notzed.nativez/bin/%
+ install -D $< $@
+
+export_CXXSOURCES = export.cc
+export_SOURCES = tree-codes.c
+export_CXXFLAGS = -Wno-switch -g
+export_CPPFLAGS=-I. -I$(GCCPLUGINDIR)/include
--- /dev/null
+/*
+ This builds a tree-code to name table, they are indexed
+ by position.
+ TODO: Not sure if there is some debug function to get them directly.
+ */
+#define END_OF_BASE_TREE_CODES "last_and_unused_tree_code"
+#define DEFTREECODE(a, b, c, d) b,
+const char *tree_codes[] = {
+#include <all-tree.def>
+};