From 26b85e106e06296e8b67b4536a0cc5f792c4149e Mon Sep 17 00:00:00 2001 From: Not Zed Date: Fri, 17 Jun 2022 19:12:03 +0930 Subject: [PATCH] Convert to using jdk.foreign rather than jni. --- Makefile | 2 +- README | 218 ++- config.make.in | 7 + java.make | 334 +++-- nbproject/project.properties | 4 +- src/notzed.nativez/bin/export-api | 159 +++ src/notzed.nativez/bin/export-defines | 396 ++++++ src/notzed.nativez/bin/generate-api | 403 ++++++ .../classes/au/notzed/nativez/Array.java | 8 + .../classes/au/notzed/nativez/ByteArray.java | 98 ++ .../au/notzed/nativez/DoubleArray.java | 90 ++ .../classes/au/notzed/nativez/FloatArray.java | 90 ++ .../classes/au/notzed/nativez/Frame.java | 249 ++++ .../au/notzed/nativez/FunctionPointer.java | 7 + .../au/notzed/nativez/HandleArray.java | 117 ++ .../classes/au/notzed/nativez/IntArray.java | 102 ++ .../classes/au/notzed/nativez/LongArray.java | 86 ++ .../classes/au/notzed/nativez/Memory.java | 251 ++++ .../classes/au/notzed/nativez/Native.java | 436 ++++++ .../classes/au/notzed/nativez/NativeZ.java | 552 -------- .../classes/au/notzed/nativez/Pointer.java | 18 + .../au/notzed/nativez/PointerArray.java | 86 ++ .../classes/au/notzed/nativez/ShortArray.java | 90 ++ src/notzed.nativez/classes/module-info.java | 3 +- src/notzed.nativez/jni/jni.make | 21 - src/notzed.nativez/jni/nativez-gen | 199 --- src/notzed.nativez/jni/nativez-jni.c | 368 ----- src/notzed.nativez/jni/nativez-jni.def | 22 - src/notzed.nativez/jni/nativez-linux.c | 94 -- src/notzed.nativez/jni/nativez-windows.c | 93 -- src/notzed.nativez/jni/nativez.h | 203 --- src/notzed.nativez/lib/api.pm | 1107 +++++++++++++++ src/notzed.nativez/lib/code.api | 439 ++++++ src/notzed.nativez/lib/code.pm | 320 +++++ src/notzed.nativez/lib/config.pm | 183 +++ src/notzed.nativez/lib/method.pm | 175 +++ src/notzed.nativez/lib/tokenise.pm | 135 ++ src/notzed.nativez/lib/types.api | 283 ++++ src/notzed.nativez/native/export.cc | 1227 +++++++++++++++++ src/notzed.nativez/native/list.h | 200 +++ src/notzed.nativez/native/native.make | 16 + src/notzed.nativez/native/tree-codes.c | 10 + 42 files changed, 7073 insertions(+), 1828 deletions(-) create mode 100755 src/notzed.nativez/bin/export-api create mode 100755 src/notzed.nativez/bin/export-defines create mode 100755 src/notzed.nativez/bin/generate-api create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/Array.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/DoubleArray.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/FloatArray.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/Frame.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/FunctionPointer.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/HandleArray.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/IntArray.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/LongArray.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/Memory.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/Native.java delete mode 100644 src/notzed.nativez/classes/au/notzed/nativez/NativeZ.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/Pointer.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/PointerArray.java create mode 100644 src/notzed.nativez/classes/au/notzed/nativez/ShortArray.java delete mode 100644 src/notzed.nativez/jni/jni.make delete mode 100755 src/notzed.nativez/jni/nativez-gen delete mode 100644 src/notzed.nativez/jni/nativez-jni.c delete mode 100644 src/notzed.nativez/jni/nativez-jni.def delete mode 100644 src/notzed.nativez/jni/nativez-linux.c delete mode 100644 src/notzed.nativez/jni/nativez-windows.c delete mode 100644 src/notzed.nativez/jni/nativez.h create mode 100644 src/notzed.nativez/lib/api.pm create mode 100644 src/notzed.nativez/lib/code.api create mode 100644 src/notzed.nativez/lib/code.pm create mode 100644 src/notzed.nativez/lib/config.pm create mode 100644 src/notzed.nativez/lib/method.pm create mode 100644 src/notzed.nativez/lib/tokenise.pm create mode 100644 src/notzed.nativez/lib/types.api create mode 100644 src/notzed.nativez/native/export.cc create mode 100644 src/notzed.nativez/native/list.h create mode 100644 src/notzed.nativez/native/native.make create mode 100644 src/notzed.nativez/native/tree-codes.c diff --git a/Makefile b/Makefile index 55f2a0a..6b5c0a5 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -dist_VERSION=1.0.99 +dist_VERSION=2.0.99-jdk-foreign dist_NAME=nativez dist_EXTRA=README \ build.xml \ diff --git a/README b/README index da90562..1fa1148 100644 --- a/README +++ b/README @@ -6,12 +6,9 @@ See the section LICENSE for details. 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 ------- @@ -30,179 +27,120 @@ The target-specific jmodule: `bin//jmods/notzed.nativez.jmod' +$ make sdk + +Will compile a usable in-tree version to bin//bin. + Other files of import which can be used for an IDE, dependent project compilation, or non-modular use: -`bin//lib/notzed.nativez.jar' - portable modular jar -`bin//include' - header files -`bin//lib/libnativez.so' - linux shared library -`bin//bin/nativez.dll' - microsoft windows dynamic link library -`bin//bin/nativez-gen' - table generator +`bin//lib/notzed.nativez.jar' - portable modular jar for utility classes. +`bin//lib/libexport.so' - gcc plugin for extracting C api information. +`bin//bin/export-api' - driver script for api generation. +`bin//bin/generate-api' - internal script for C structs/functions. +`bin//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//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. +_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//gen/name.api` - api control file +`src//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 .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 `` can be used to apply to all structures defined by +including `.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 -#inclulde +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 ------- diff --git a/config.make.in b/config.make.in index 18b53d1..72e4bba 100644 --- a/config.make.in +++ b/config.make.in @@ -6,6 +6,7 @@ JAVA_HOME ?= /usr/local/jdk JAVAC ?= $(JAVA_HOME)/bin/javac JAR ?= $(JAVA_HOME)/bin/jar JMOD ?= $(JAVA_HOME)/bin/jmod +GCCPLUGINDIR:=$(shell gcc -print-file-name=plugin) JAVACFLAGS += @@ -14,6 +15,8 @@ linux-amd64_CPPFLAGS = \ -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 @@ -27,7 +30,11 @@ windows-amd64_CPPFLAGS = \ 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)) diff --git a/java.make b/java.make index ee876b1..c3b9030 100644 --- a/java.make +++ b/java.make @@ -1,5 +1,5 @@ # -# Copyright (C) 2019 Michael Zucchi +# Copyright (C) 2019,2022 Michael Zucchi # # This is the copyright for java.make # @@ -31,6 +31,9 @@ # generators must exist in src//gen. Native # libraries must exist in src//jni. +# native_MODULES list of native-only "modules". + + # Global variables # JAVA_HOME location of jdk. @@ -40,6 +43,7 @@ # JARFLAGS jar flags # JMOD jmod command. # JMODFLAGS jmod flags. +# JAVAFLAGS java flags for run targets # Module specific variables @@ -49,9 +53,12 @@ # _JARFLAGS # _JMODFLAGS +# all paths are relative to the root package name + # _JAVA Java sources. If not set it is found from src//classes/(*.java) # _RESOURCES .jar resources. If not set it is found from src//classes/(not *.java) -# _JAVA_GENERATED Java generated sources. These must be relative to the package name. +# _JAVA_GENERATED Java generated sources. +# _RESOURCES_GENERATED Java generated sources. # Variables for use in fragments @@ -59,7 +66,6 @@ # _gendir Location for files used in Java generation process (per project). # _genjavadir Location where _JAVA_GENERATED .java files will be created (per project). -# _jnidir Location for jni generated files (per target). # _objdir Location for c objects (per target). # _incdir Location for output includes, .jmod staging. # _libdir Location for output libraries, .jmod staging. May point to _bindir. @@ -73,7 +79,7 @@ # These are compiled after the java sources have been compiled as that # process also generates any native binding headers. -# _JNI_LIBRARIES list of libraries to build. +# _NATIVE_LIBRARIES list of libraries to build. # library names match System.loadLibrary(). # Global variables @@ -82,6 +88,9 @@ # _LDLIBS # _CPPFLAGS # _CFLAGS +# _CC +# _CXXFLAGS +# _CXX # SO shared library suffix # LIB shared library prefix @@ -91,22 +100,21 @@ # Per library variables. -# _SOURCES .c, .cc, .C - source files for library. Paths are relative to src//jni. -# _HEADERS header files for jmod -# _COMMANDS commands/bin/scripts for jmod +# _SOURCES .c source files for library. Paths are relative to src//native. +# _CXXSOURCES .c source files for library. Paths are relative to src//native. +# _HEADERS header files for install/jmod +# _COMMANDS commands/bin/scripts for install/jmod # _LDFLAGS link flags # _LIBADD extra objects to add to link line # _LDLIBS link libraries -# _CPPFLAGS c pre-processor flags. "-Isrc//jni -Ibin/include/" is implicit. +# _CPPFLAGS c and c++ pre-processor flags. "-Isrc//jni -Ibin/include/" is implicit. # _CCFLAGS c compiler flags +# _CXXFLAGS c++ compiler flags # _DEPENDENCIES A list of other objects on which this library depends before linking. -# _DEFS A list of .def files for nativez-gen. -# _DEFSFLAGS Flags for nativez-gen invocation. - -# .c files have dependencies automatically generated +# .c and .cc files have dependencies automatically generated # Targets # ------- @@ -115,7 +123,6 @@ # 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 # ------- @@ -146,30 +153,29 @@ # ###################################################################### +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 @@ -179,45 +185,117 @@ java_jardir:=bin/$(TARGET)/lib 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: +# _sdk is the target location of an expanded 'sdk' for this module +# it resides in a common location bin// +# _jmod is the target location of a staging area for jmod files +# is resides in a per-module lcoation bin/// +# _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/.depjava marks all source/generated sources are ready/updated +# bin/status/.depjar all compiled class files and resources are ready/updated +# bin/status/.sdk all files are available in bin/ 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 \ @@ -231,113 +309,108 @@ $(java_jmoddir)/$(1).jmod: bin/status/$(1).classes bin/status/$(1).data $$@ # 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//* to sdk area bin//* $(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))))) # ###################################################################### @@ -345,6 +418,5 @@ dist: @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) - diff --git a/nbproject/project.properties b/nbproject/project.properties index 81e958a..e20f7a8 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -47,8 +47,8 @@ javac.modulepath= 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=\ diff --git a/src/notzed.nativez/bin/export-api b/src/notzed.nativez/bin/export-api new file mode 100755 index 0000000..6ecd12d --- /dev/null +++ b/src/notzed.nativez/bin/export-api @@ -0,0 +1,159 @@ +#!/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"); diff --git a/src/notzed.nativez/bin/export-defines b/src/notzed.nativez/bin/export-defines new file mode 100755 index 0000000..7b986e0 --- /dev/null +++ b/src/notzed.nativez/bin/export-defines @@ -0,0 +1,396 @@ +#!/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 thing + if ($export->{default} eq 'all') { + unshift @{$export->{items}}, { regex => qr//, 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 < +#include +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 <'%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 <{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 < \\"$d->{name}\\", ", fp); + fprintf(fp, FMT($d->{name}), ($d->{name})); + fputs("$docomment},\\n", fp); +END + } + print $fp < '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; +} diff --git a/src/notzed.nativez/bin/generate-api b/src/notzed.nativez/bin/generate-api new file mode 100755 index 0000000..f185237 --- /dev/null +++ b/src/notzed.nativez/bin/generate-api @@ -0,0 +1,403 @@ +#!/usr/bin/perl + +# TODO: formatLibrary - code: 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:'}; + 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:') { + 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:'}; + + 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}:"}; + 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)); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Array.java b/src/notzed.nativez/classes/au/notzed/nativez/Array.java new file mode 100644 index 0000000..21635a2 --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/Array.java @@ -0,0 +1,8 @@ + +package au.notzed.nativez; + +public interface Array { + long length(); + T getAtIndex(long i); + boolean isEmpty(); +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java b/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java new file mode 100644 index 0000000..0abf605 --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java @@ -0,0 +1,98 @@ +/* + * 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 . + */ + +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 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); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/DoubleArray.java b/src/notzed.nativez/classes/au/notzed/nativez/DoubleArray.java new file mode 100644 index 0000000..9a0bfce --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/DoubleArray.java @@ -0,0 +1,90 @@ +/* + * 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 . + */ + +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 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); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/FloatArray.java b/src/notzed.nativez/classes/au/notzed/nativez/FloatArray.java new file mode 100644 index 0000000..e81a57e --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/FloatArray.java @@ -0,0 +1,90 @@ +/* + * 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 . + */ + +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 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); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Frame.java b/src/notzed.nativez/classes/au/notzed/nativez/Frame.java new file mode 100644 index 0000000..7ce52b9 --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/Frame.java @@ -0,0 +1,249 @@ +/* + * 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 . + */ +package au.notzed.nativez; + +import jdk.incubator.foreign.*; +import static jdk.incubator.foreign.ValueLayout.OfAddress; + +/** + * This is a per-thread stack-based allocator. + *
+ * try (Frame f = Memory.createFrame()) {
+ *		MemorySegment a = f.allocate(size);
+ * }
+ * 
+ * Any memory allocated is freed when the frame is closed. + *

+ * 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 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 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 MemorySegment copy(T value) { + return copy(value.address()); + } + + public 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; + } + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/FunctionPointer.java b/src/notzed.nativez/classes/au/notzed/nativez/FunctionPointer.java new file mode 100644 index 0000000..dd36722 --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/FunctionPointer.java @@ -0,0 +1,7 @@ + +package au.notzed.nativez; + +import jdk.incubator.foreign.NativeSymbol; + +public record FunctionPointer(NativeSymbol symbol, T function) { +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/HandleArray.java b/src/notzed.nativez/classes/au/notzed/nativez/HandleArray.java new file mode 100644 index 0000000..7434f65 --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/HandleArray.java @@ -0,0 +1,117 @@ +/* + * 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 . + */ + +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 extends AbstractList implements Pointer { + public final MemorySegment segment; + final ResourceScope scope; + BiFunction create; + + private HandleArray(MemorySegment segment, BiFunction create, ResourceScope scope) { + this.segment = segment; + this.create = create; + this.scope = scope; + } + + public static HandleArray create(MemorySegment segment, BiFunction create) { + return new HandleArray<>(segment, create, segment.scope()); + } + + public static HandleArray create(MemorySegment segment, BiFunction create, ResourceScope scope) { + return new HandleArray<>(segment, create, scope); + } + + public static HandleArray createArray(long size, SegmentAllocator alloc, BiFunction create) { + return create(alloc.allocateArray(Memory.POINTER, size), create); + } + + public static HandleArray createArray(long size, SegmentAllocator alloc, BiFunction create, ResourceScope scope) { + return create(alloc.allocateArray(Memory.POINTER, size), create, scope); + } + + public static HandleArray createArray(MemoryAddress address, long size, BiFunction create, ResourceScope scope) { + return create(MemorySegment.ofAddress(address, size * Memory.POINTER.byteSize(), scope), create); + } + + public static HandleArray create(SegmentAllocator alloc, BiFunction create, T... values) { + HandleArray array = create(alloc.allocateArray(Memory.POINTER, values.length), create); + for (int i=0;i asSlice(long offset) { + return create(segment.asSlice(offset * Memory.POINTER.byteSize()), create, scope); + } + + public HandleArray 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); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/IntArray.java b/src/notzed.nativez/classes/au/notzed/nativez/IntArray.java new file mode 100644 index 0000000..0f73c4b --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/IntArray.java @@ -0,0 +1,102 @@ +/* + * 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 . + */ +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 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); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/LongArray.java b/src/notzed.nativez/classes/au/notzed/nativez/LongArray.java new file mode 100644 index 0000000..9885ced --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/LongArray.java @@ -0,0 +1,86 @@ +/* + * 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 . + */ + +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 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); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Memory.java b/src/notzed.nativez/classes/au/notzed/nativez/Memory.java new file mode 100644 index 0000000..b52412a --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/Memory.java @@ -0,0 +1,251 @@ +/* + * 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 . + */ +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 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 Addressable address(FunctionPointer 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(); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Native.java b/src/notzed.nativez/classes/au/notzed/nativez/Native.java new file mode 100644 index 0000000..191728b --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/Native.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2021 Michael Zucchi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package 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. + *

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

+ * Work in progress. + *

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

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

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

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

+ * This serves two purposes: + *

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

+ * CHandle's are chained directly from the index table, the p field + * is used as a key directly, and hash values are not cached. This combines + * to save significant memory per node. + */ + private static class PointerTable { + + int mask = 63; + int size = 0; + CHandle[] table = new CHandle[64]; + + private void resize(int length) { + CHandle[] ntable = new CHandle[length]; + int nmask = length - 1; + + for (int i = 0; i < table.length; i++) { + CHandle h = table[i]; + + while (h != null) { + CHandle n = h.next; + int k = h.hashCode() & nmask; + + h.next = ntable[k]; + ntable[k] = h; + + h = n; + } + } + + table = ntable; + mask = nmask; + } + + public CHandle put(CHandle h) { + CHandle o = remove(h.p); + + putAlways(h); + + return o; + } + + public void putAlways(CHandle h) { + if (size > table.length) + resize(table.length * 2); + + int i = h.hashCode() & mask; + + h.next = table[i]; + table[i] = h; + size += 1; + } + + public CHandle get(MemoryAddress p) { + int i = CHandle.hashCode(p) & mask; + CHandle h = table[i]; + + while (h != null && !h.p.equals(p)) + h = h.next; + return h; + } + + public CHandle remove(MemoryAddress p) { + int i = CHandle.hashCode(p) & mask; + CHandle h = table[i]; + CHandle a = null; + + while (h != null && !h.p.equals(p)) { + a = h; + h = h.next; + } + if (h != null) { + if (a != null) + a.next = h.next; + else + table[i] = h.next; + size -= 1; + } + + return h; + } + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/NativeZ.java b/src/notzed.nativez/classes/au/notzed/nativez/NativeZ.java deleted file mode 100644 index 7b3c266..0000000 --- a/src/notzed.nativez/classes/au/notzed/nativez/NativeZ.java +++ /dev/null @@ -1,552 +0,0 @@ -/* - * Copyright (C) 2018 Michael Zucchi - * - * This file is part of nativez - * - * 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. - *

- */ -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. - *

- * 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 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. - *

- * The module must be open to notzed.nativez for this to work. - * - * @param - * @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 createInstance(Class jtype, long p) { - cleanerStep(); - try { - Class[] params = {Long.TYPE}; - Constructor 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. - *

- * Use this function if the object does not use the default constructor - * or has been created separately. - *

- * Do not use this for internally reference counted resources. - * - * @param - * @param o The object to register. - * @return o is returned. - */ - public static 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. - *

- * Use this function when possible if using the default constructor - * as it saves a native to managed transition and also allows - * for concurrent instantiation. - *

- * 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 create(Class 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. - *

- * 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. - *

- * If the resources are managed natively via reference counting then this - * function must be used to ensure a unique instance is created. - *

- * 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 resolve(Class 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. - *

- * This is a way to access internal members and other data which is - * not allocated explicitly. - *

- * 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. - *

- * 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 refer(Class 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. - *

- * The object is advanced to the refernce queue immediately. - *

- * Applications MUST NOT access this object afterwards, even if - * they retain references to it. - */ - public void release() { - WeakReference 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. - *

- * A convenience function to release multiple resources explicitly. - *

- * 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. - *

- * 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. - *

- * 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 { - - protected long p; - final Class jtype; - CHandle next; - - public CHandle(NativeZ referent, ReferenceQueue 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. - *

- * 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. - *

- * 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 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. - *

- * 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 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. - *

- * This serves two purposes: - *

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

- * CHandle's are chained directly from the index table, the p field - * is used as a key directly, and hash values are not cached. This combines - * to save significant memory per node. - */ - private static class PointerTable { - - int mask = 63; - int size = 0; - CHandle[] table = new CHandle[64]; - - private void resize(int length) { - CHandle[] ntable = new CHandle[length]; - int nmask = length - 1; - - for (int i = 0; i < table.length; i++) { - CHandle h = table[i]; - - while (h != null) { - CHandle n = h.next; - int k = h.hashCode() & nmask; - - h.next = ntable[k]; - ntable[k] = h; - - h = n; - } - } - - table = ntable; - mask = nmask; - } - - public CHandle put(CHandle h) { - CHandle o = remove(h.p); - - putAlways(h); - - return o; - } - - public void putAlways(CHandle h) { - if (size > table.length * 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; - } - } -} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Pointer.java b/src/notzed.nativez/classes/au/notzed/nativez/Pointer.java new file mode 100644 index 0000000..5b003dc --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/Pointer.java @@ -0,0 +1,18 @@ + +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; + //} +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/PointerArray.java b/src/notzed.nativez/classes/au/notzed/nativez/PointerArray.java new file mode 100644 index 0000000..cdc4189 --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/PointerArray.java @@ -0,0 +1,86 @@ +/* + * 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 . + */ + +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 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); + } +} diff --git a/src/notzed.nativez/classes/au/notzed/nativez/ShortArray.java b/src/notzed.nativez/classes/au/notzed/nativez/ShortArray.java new file mode 100644 index 0000000..f93599d --- /dev/null +++ b/src/notzed.nativez/classes/au/notzed/nativez/ShortArray.java @@ -0,0 +1,90 @@ +/* + * 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 . + */ + +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 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); + } +} diff --git a/src/notzed.nativez/classes/module-info.java b/src/notzed.nativez/classes/module-info.java index 84a8720..7edcf0a 100644 --- a/src/notzed.nativez/classes/module-info.java +++ b/src/notzed.nativez/classes/module-info.java @@ -1,6 +1,5 @@ module notzed.nativez { - requires java.logging; - + requires transitive jdk.incubator.foreign; exports au.notzed.nativez; } diff --git a/src/notzed.nativez/jni/jni.make b/src/notzed.nativez/jni/jni.make deleted file mode 100644 index f2cad7d..0000000 --- a/src/notzed.nativez/jni/jni.make +++ /dev/null @@ -1,21 +0,0 @@ - -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 $< $@ diff --git a/src/notzed.nativez/jni/nativez-gen b/src/notzed.nativez/jni/nativez-gen deleted file mode 100755 index 5a6a176..0000000 --- a/src/notzed.nativez/jni/nativez-gen +++ /dev/null @@ -1,199 +0,0 @@ -#!/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 () { - 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 () { - 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//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"; -} diff --git a/src/notzed.nativez/jni/nativez-jni.c b/src/notzed.nativez/jni/nativez-jni.c deleted file mode 100644 index 77c553c..0000000 --- a/src/notzed.nativez/jni/nativez-jni.c +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright (C) 2017,2019 Michael Zucchi - * - * This file is part of nativez - * - * 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 -#include -#include - -#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; -} diff --git a/src/notzed.nativez/jni/nativez-jni.def b/src/notzed.nativez/jni/nativez-jni.def deleted file mode 100644 index 3fa29af..0000000 --- a/src/notzed.nativez/jni/nativez-jni.def +++ /dev/null @@ -1,22 +0,0 @@ -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 -} diff --git a/src/notzed.nativez/jni/nativez-linux.c b/src/notzed.nativez/jni/nativez-linux.c deleted file mode 100644 index 36aae60..0000000 --- a/src/notzed.nativez/jni/nativez-linux.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2019 Michael Zucchi - * - * This file is part of nativez - * - * 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 -#include -#include - -#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; -} diff --git a/src/notzed.nativez/jni/nativez-windows.c b/src/notzed.nativez/jni/nativez-windows.c deleted file mode 100644 index 1602c98..0000000 --- a/src/notzed.nativez/jni/nativez-windows.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2019 Michael Zucchi - * - * This file is part of nativez - * - * 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 - -#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; -} diff --git a/src/notzed.nativez/jni/nativez.h b/src/notzed.nativez/jni/nativez.h deleted file mode 100644 index 51d862c..0000000 --- a/src/notzed.nativez/jni/nativez.h +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2018 Michael Zucchi - * - * This file is part of nativez - * - * 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 - -/** - * 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 diff --git a/src/notzed.nativez/lib/api.pm b/src/notzed.nativez/lib/api.pm new file mode 100644 index 0000000..6b46c70 --- /dev/null +++ b/src/notzed.nativez/lib/api.pm @@ -0,0 +1,1107 @@ + +# TODO: define more & consistent stage processing hooks, currently implicit require init() and stage.postprocess +# TODO: code: etc should probably instead be code: where 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:' => { + name => '', + items => [], + options => [ 'default=none', 'access=rw', 'field:rename=studly-caps' ], + regex => qr/^struct:$/, + type => 'struct' + }, + 'union:' => { + name => '', + items => [], + options => [ 'default=all', 'access=rw', 'field:rename=studly-caps' ], + regex => qr/^union:$/, + type => 'union' + }, + 'call:' => { + name => '', + items => [], + options => [ 'call:rename=call', 'access=r' ], + regex => qr/^call:$/, + type => 'call' + }, + 'func:' => { + name => '', + items => [], + options => [], + regex => qr/^func:$/, + type => 'func', + 'func:template' => 'code:method=invoke' + }, + 'enum:' => { + name => '', + items => [], + options => [], + regex => qr/^enum:$/, + 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}:"}; + + 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}:"}; + 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}:"}; + 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:'}) 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}:"}, $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@^$@) { + $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@^$@) { + $inc->{matcher} = eval "sub { $inc->{literal} }"; + die "unable to parse match function $inc->{literal} $! $@" if !defined($inc->{matcher}); + } elsif ($match =~ m@^$@) { + $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}:"}; + + #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}:"}; + + 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}:"}; + 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: 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}:"}; + + 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 ''} @{$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; diff --git a/src/notzed.nativez/lib/code.api b/src/notzed.nativez/lib/code.api new file mode 100644 index 0000000..651dd55 --- /dev/null +++ b/src/notzed.nativez/lib/code.api @@ -0,0 +1,439 @@ +# -*- 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 resolve, ResourceScope scope) { +{init} + } + public static {name} create(Function 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); + } + } + }} +} diff --git a/src/notzed.nativez/lib/code.pm b/src/notzed.nativez/lib/code.pm new file mode 100644 index 0000000..9383602 --- /dev/null +++ b/src/notzed.nativez/lib/code.pm @@ -0,0 +1,320 @@ +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; diff --git a/src/notzed.nativez/lib/config.pm b/src/notzed.nativez/lib/config.pm new file mode 100644 index 0000000..21a3973 --- /dev/null +++ b/src/notzed.nativez/lib/config.pm @@ -0,0 +1,183 @@ +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; diff --git a/src/notzed.nativez/lib/method.pm b/src/notzed.nativez/lib/method.pm new file mode 100644 index 0000000..608cb78 --- /dev/null +++ b/src/notzed.nativez/lib/method.pm @@ -0,0 +1,175 @@ + +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; +} diff --git a/src/notzed.nativez/lib/tokenise.pm b/src/notzed.nativez/lib/tokenise.pm new file mode 100644 index 0000000..478ed73 --- /dev/null +++ b/src/notzed.nativez/lib/tokenise.pm @@ -0,0 +1,135 @@ + +# +# simple tokeniser +# +# tokens are +# 'self' : '{' ';' '}' +# 'token':'\S+' +# 'literal': '{{.*}}\n' may span multiple lines but final }} +# must be at end of line + +package tokenise; + +use Data::Dumper; +use strict; + +sub new { + my $class = shift; + my $path = shift; + my $self = { + state => 0, + line => [], + colno => 0, + type => 'none', + files => [], + file => { path=>$path, lineno=>0, lines=>[] } + }; + + open (my $in,"<",$path) || die("unable to open '$path': $@"); + @{$self->{file}->{lines}} = <$in>; + close $in; + + bless $self, $class; + return $self; +} + +# include a file at the current point +sub include { + my $self = shift; + my $path = shift; + + #print "include '$path'\n"; + + push @{$self->{files}}, $self->{file}; + + $self->{file} = { path=>$path, lineno=>0, lines=>[] }; + + open (my $in,"<",$path) || die("unable to open '$path': $@"); + @{$self->{file}->{lines}} = <$in>; + close $in; + + $self->{state} = 0; + # should it swallow the rest of {line}? +} + +sub next { + my $self = shift; + my $token; + + #print "next $self->{state} $#{$self->{lines}} $#{$self->{line}}\n"; + # on entry state is in [0, 3, 4] + while ($#{$self->{file}->{lines}} >= 0 || $#{$self->{line}} >= 0 || $#{$self->{files}} >= 0) { + if ($#{$self->{line}} < 0) { + if ($#{$self->{file}->{lines}} < 0) { + $self->{file} = pop @{$self->{files}}; + } + my $t = shift @{$self->{file}->{lines}}; + $t =~ s/(?:^\#.*|\s+\#.*)//; + push @{$self->{line}}, split //,$t; + $self->{file}->{lineno} += 1; + $self->{colno} = 0; + } + my $c = shift @{$self->{line}}; + + $self->{colno} += 1; + + #printf("$self->{state} '$c'=\$%02x\n", ord($c)); + + if ($self->{state} == 0) { + if (($c =~ m/\s/)) { + next; + } elsif ($c =~ m/[;\}]/) { + $self->{type} = $c; + #print "char: $c\n"; + return $c; + } elsif ($c eq '{') { + if ($#{$self->{line}} >= 0 && $self->{line}->[0] eq '{') { + shift @{$self->{line}}; + $self->{state} = 3; + } else { + $self->{type} = $c; + #print "char: $c\n"; + return $c; + } + } elsif ($c) { + $token .= $c; + $self->{state} = 1; + } + } elsif ($self->{state} == 1) { + if ($c eq ';') { + unshift @{$self->{line}}, $c; + $self->{state} = 2; + $self->{type} = 'token'; + #print "tok: $token\n"; + return $token; + } elsif ($c =~ m/\S/) { + $token .= $c; + } else { + $self->{state} = 0; + $self->{type} = 'token'; + #print "tok: $token\n"; + return $token; + } + } elsif ($self->{state} == 2) { + $self->{state} = 0; + #print "char: $c\n"; + return $c; + } elsif ($self->{state} == 3) { + if ($c eq '}' && $#{$self->{line}} == 1 && $self->{line}->[0] eq '}') { + shift @{$self->{line}}; + $self->{state} = 0; + $self->{type} = 'literal'; + #print "lit: $token\n"; + return $token; + } else { + $token .= $c; + } + } + } + + die "invalid state=$self->{state}" if $self->{state} != 0; + + return undef; +} + +1; diff --git a/src/notzed.nativez/lib/types.api b/src/notzed.nativez/lib/types.api new file mode 100644 index 0000000..9c3c91a --- /dev/null +++ b/src/notzed.nativez/lib/types.api @@ -0,0 +1,283 @@ +# -*- 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 like code:method? +type /bitfield/ copy= { + layout; + type {{ bitfieldType($m) }} + carrier {{ "{type}" }} + tonative {{ "({type})({value})" }} + tojava {{ "({type})({value})" }} + + getshiftl {{ bitfieldSize($m) - $m->{size} - bitfieldOffset($m) }} + getshiftr {{ bitfieldSize($m) - $m->{size} }} + getshiftop {{ ($m->{type} =~ m/^i/) ? ">>" : ">>>" }} + + setmask {{ bitfieldMask($m) }} + setoffset {{ bitfieldOffset($m) }} + + varindex {{ bitfieldIndex($m) }} + + getnative {{ + "({type})((({type}){name}\$VH.get({segment}) << {getshiftl}) {getshiftop} {getshiftr})" + }} + setnative {{ + "$m->{name}\$VH.set({segment}, ({type})((({type}){name}\$VH.get({segment}) & ~{setmask}) | ((({value}) << {setoffset}) & {setmask})))" + }} + varhandle {{ "final static VarHandle {name}\$VH = MemoryLayout.sequenceLayout(Memory.".uc(bitfieldType($m)).").varHandle(MemoryLayout.PathElement.sequenceElement({varindex}));\n" }} +} + +# void, only for return type +type /void/ { + type {{ 'void' }} + carrier {{ 'void' }} + getnative; + setnative; + tonative; + tojava; +} + +# 1d array of pointer to pointer +type /^\[(\d+)u64:u64.*\]$/ { +} + +# 1d array of pointer to primitive +type /^\[(?\d+)u64:(?[uif]\d+)\]$/ + copy= template=code:getbyvalue,code:getsetelement { + + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }} + layout {{ 'MemoryLayout.sequenceLayout({length}, Memory.POINTER)' }} + tojava {{ !$m->{array} ? 'PointerArray.createArray({value}, {length}, {scope})' : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})' }} + + getsegment {{ '(MemorySegment){name}$SH.invokeExact({segment})' }} + getnative {{ !$m->{array} ? 'PointerArray.create({getsegment})' : 'HandleArray.create({getsegment}, (a, s) -> {typei}.createArray(a, Long.MAX_VALUE, s))' }} + setnative; + varhandle {{ + 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n". + 'final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement());'."\n" + }} + + typei {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }} + getnativei {{ '{typei}.createArray((MemoryAddress){name}$EH.get({segment}, {index}), Long.MAX_VALUE, {scope})' }} + setnativei {{ '{name}$EH.set({segment}, {index}, (Addressable)Memory.address({value}))' }} + +} + +# 1d array of pointer to type (struct or union or function?) +type /^\[(?\d+)u64:\$\{(\w+)\}\]$/ { + type {{ "HandleArray<$data->{$m->{type}}->{rename}>" }} + layout {{ "MemoryLayout.sequenceLayout({length}, Memory.POINTER)" }} +} + +# 1d array of (struct or union or function?) +type /^\[(?\d+)\$\{(\w+)\}\]$/ 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 /^\[(?\d+)(?[uif]\d+)\]$/ + copy= template=code:getbyvalue,code:getsetelement { + layout {{ "MemoryLayout.sequenceLayout({length}, Memory.".uc($typeSizes{$match->{ctype}}).")" }} + type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + tojava {{ "{type}.createArray({value}, $match->{length}, {scope})" }} + tonative {{ "(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 /^\[(?\d+)\[(?\d+)(?[uif]\d+)\]\]$/ + copy= template=code:getbyvalue,code:getsetelement2d { + layout {{ "MemoryLayout.sequenceLayout({length0}, MemoryLayout.sequenceLayout({length1}, Memory.".uc($typeSizes{$match->{ctype}})."))" }} + type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + tojava {{ "{type}.create({value})" }} + tonative {{ "(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 is an 'in' only pointer to pointer +type /^u64:u64:/ select=raw-in copy= { + tonative {{ '(Addressable)frame$.allocate(Memory.POINTER, {value})' }} +} + +# function pointer +type /^u64:\(/ copy= { + type {{ "FunctionPointer<$data->{$m->{type}}->{rename}>" }} + tojava {{ "$data->{$m->{type}}->{rename}.downcall({value}, {scope})" }} +} + +# **primitive +type /^u64:u64:(?[uif]\d+)$/ copy= { + length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }} + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }} + tojava {{ + !$m->{array} + ? 'PointerArray.createArray({value}, {length}, {scope})' + : 'HandleArray.createArray({value}, {length}, (a, s) -> {typei}.createArray(a, {length}, s), {scope})' + }} + typei {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} +} + +# **type +type /^u64:u64:\$\{(\w+)\}$/ copy= { + length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }} + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }} + tojava {{ + !$m->{array} + ? 'PointerArray.createArray({value}, {length}, {scope})' + : 'HandleArray.createArray({value}, {length}, {typei}::create, {scope})' + }} + # tojavai ... ? + carrieri {{ "MemoryAddress" }} + typei {{ "$data->{$m->{type}}->{rename}" }} + tojavai {{ "{typei}.create({value}, {scope})" }} +} + +# **void +type /^u64:u64:v$/ copy= { + length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }} + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray' }} + 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:(?[ui]8)$/ select=array copy= { + type {{ ucfirst($typeSizes{$match->{ctype}}).'Array' }} + tojava {{ '{type}.createArray({value}, Long.MAX_VALUE, {scope})' }} + carrieri {{ "{typei}" }} + typei {{ $typeSizes{$match->{ctype}} }} + tojavai {{ "({typei}){value}" }} +} + +# *i8 with 'segment' flag, length must be supplied (somewhere??) +type /^u64:(?[ui]8)$/ select=segment copy= { + type {{ 'MemorySegment' }} + tojava {{ '{value}' }} + carrieri {{ "{typei}" }} + typei {{ 'byte' }} + tojavai {{ "({typei}){value}" }} +} + +# *i8 with no flag = String +type /^u64:(?i8)$/ copy= { + type {{ 'String' }} + tonative {{ '(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:(?[uif]\d+)$/ copy= { + 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= { + 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= { + type {{ "$data->{$m->{type}}->{rename}" }} + tojava {{ "$data->{$m->{type}}->{rename}.create({value}, {scope})" }} +} + +# *void with size +type /^u64:v$/ select=array-size copy= { + type {{ 'MemorySegment' }} + length {{ 'get'.($m->{'array-size'}->{rename}).'()' }} + tojava {{ "MemorySegment.ofAddress({value}, {length}, {scope})" }} +} + +# *void +type /^u64:v$/ copy= { +} + +# primitive +type /^(?[uif]\d+)$/ copy= { + layout {{ "Memory.".uc($typeSizes{$match->{ctype}}) }} + carrier {{ "$typeSizes{$match->{ctype}}" }} + type {{ "$typeSizes{$match->{ctype}}" }} + tonative {{ '({type})({value})' }} + tojava {{ '({type})({value})' }} +} + +# type inline +type /^\$\{(\w+)\}$/ byvalue template=code:getbyvalue { + layout {{ '{type}.LAYOUT' }} + carrier {{ '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 template=code:getset { + getnative {{ '({carrier}){name}$VH.get({segment})' }} + setnative {{ '{name}$VH.set({segment}, {tonative})' }} + varhandle {{ 'final static VarHandle {name}$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n" }} + tojava {{ '{value}' }} + tonative {{ '{value}' }} +} + +type copy= { + layout {{ 'Memory.POINTER' }} + carrier {{ 'MemoryAddress' }} + type {{ 'MemoryAddress' }} + tonative {{ '(Addressable)Memory.address({value})' }} +} diff --git a/src/notzed.nativez/native/export.cc b/src/notzed.nativez/native/export.cc new file mode 100644 index 0000000..d8b183b --- /dev/null +++ b/src/notzed.nativez/native/export.cc @@ -0,0 +1,1227 @@ +/* + * 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 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 +#include +#include +#include +#include +#include +#include +#include + +#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)) : ""); + fprintf(stderr, " field_type name '%s'\n", TYPE_IDENTIFIER(field_type) ? IDENTIFIER_POINTER(TYPE_IDENTIFIER(field_type)) : ""); + 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)) : ""); + 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 ""; +} + +/* + 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)) : "", + 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)) : ""); + 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=\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; +} diff --git a/src/notzed.nativez/native/list.h b/src/notzed.nativez/native/list.h new file mode 100644 index 0000000..4a96688 --- /dev/null +++ b/src/notzed.nativez/native/list.h @@ -0,0 +1,200 @@ + +/* + 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 + . +*/ + +/* ********************************************************************** + * 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; +} diff --git a/src/notzed.nativez/native/native.make b/src/notzed.nativez/native/native.make new file mode 100644 index 0000000..48d90af --- /dev/null +++ b/src/notzed.nativez/native/native.make @@ -0,0 +1,16 @@ + +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 diff --git a/src/notzed.nativez/native/tree-codes.c b/src/notzed.nativez/native/tree-codes.c new file mode 100644 index 0000000..ca6f237 --- /dev/null +++ b/src/notzed.nativez/native/tree-codes.c @@ -0,0 +1,10 @@ +/* + 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 +}; -- 2.39.2