diff --git a/Makefile b/Makefile index 4689b16e..37ffabb2 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +############################ +# Configurable options +############################ # If the defaults for LLVM_CONFIG are not right for your installation # create a Makefile.inc file and point LLVM_CONFIG at the llvm-config binary for your llvm distribution @@ -6,17 +9,118 @@ -include Makefile.inc -# Debian packages name llvm-config with a version number - list them here in preference order +# Set PREFIX to customize the location of the Terra installation. +PREFIX ?= /usr/local +INSTALL_BINARY_DIR ?= $(PREFIX)/bin +INSTALL_LIBRARY_DIR ?= $(PREFIX)/lib +INSTALL_SHARE_DIR ?= $(PREFIX)/share +INSTALL_INCLUDE_DIR ?= $(PREFIX)/include +INSTALL_LUA_LIBRARY_DIR ?= $(PREFIX)/lib + +# Set LLVM_CONFIG to control which LLVM installation is used to build Terra. LLVM_CONFIG ?= $(shell which llvm-config-3.5 llvm-config | head -1) -#luajit will be downloaded automatically (it's much smaller than llvm) -#to override this, set LUAJIT_PREFIX to the home of an already installed luajit -LUAJIT_PREFIX ?= build -# same with clang -CLANG ?= $(shell which clang-3.5 clang | head -1) +# By default, use Clang from the same LLVM install directory. +CLANG ?= $(shell $(LLVM_CONFIG) --bindir)/clang + +# Set CUDA_HOME to control which CUDA installation is used to build Terra. +# If this path does not exist, Terra will not be built with CUDA enabled. +CUDA_HOME ?= /usr/local/cuda +ENABLE_CUDA ?= $(shell test -e $(CUDA_HOME) && echo 1 || echo 0) -CXX ?= $(CLANG)++ -CC ?= $(CLANG) +# LuaJIT will be downloaded automatically (it's much smaller than LLVM). +# To override this, set LUA_PREFIX to the home of an already installed LuaJIT. +LUA_PREFIX ?= $(abspath build) + +PIC_FLAG ?= -fPIC +FLAGS=$(CFLAGS) + +# top-level build rule, must be first +EXECUTABLE = release/bin/terra +ifeq ($(UNAME), Darwin) +DYNLIBRARY = release/lib/terra.dylib +else +DYNLIBRARY = release/lib/terra.so +endif +.PHONY: all clean purge test release install +all: $(EXECUTABLE) $(DYNLIBRARY) + +UNAME := $(shell uname) +ifeq ($(UNAME), Darwin) +WGET = curl -o +LUA_TARGET = macosx +else +WGET = wget -O +LUA_TARGET = linux +endif + +ifneq ($(strip $(TERRA_RPATH)),) +TERRA_RPATH_FLAGS = -Wl,-rpath $(TERRA_RPATH) +endif + +########################### +# Sanity Checks +########################### + +ifeq (,$(wildcard $(shell which $(CLANG) | head -1))) + $(error clang could not be found; please set the CLANG and LLVM_CONFIG variables) +endif + +ifeq (,$(wildcard $(shell which $(LLVM_CONFIG) | head -1))) + $(error llvm-config could not be found; please set the CLANG and LLVM_CONFIG variables) +endif + +############################ +# Rules for building Lua/JIT +############################ + +TERRA_USE_PUC_LUA ?= +ifeq ($(strip $(TERRA_USE_PUC_LUA)),1) + +# PUC Lua +LUA_VERSION ?= 5.1.5 +LUA_BASENAME = lua-$(strip $(LUA_VERSION)) +LUA_TAR = $(LUA_BASENAME).tar.gz +LUA_URL = https://www.lua.org/ftp/$(LUA_TAR) +LUA_DIR = build/$(LUA_BASENAME) +LUA_LIB = $(LUA_PREFIX)/lib/liblua.a +LUA_INCLUDE = $(LUA_PREFIX)/include +LUA = $(LUA_PREFIX)/bin/lua +FLAGS += -DTERRA_USE_PUC_LUA +FLAGS += -DLUA_COMPAT_ALL # For Lua 5.2+ + +#rule for packaging lua code into bytecode, put into a header file via genheader.lua +build/%.bc: src/%.lua $(PACKAGE_DEPS) $(LUA_LIB) + $(LUA_PREFIX)/bin/luac -o $@ $< + + +else + +# LuaJIT + +# Add the following lines to Makefile.inc to switch to LuaJIT-2.1 beta releases +#LUA_VERSION_BASE =2.1 +#LUA_VERSION_EXTRA =.0-beta3 + +LUA_VERSION_BASE ?= 2.0 +LUA_VERSION_EXTRA ?= .5 +LUA_VERSION ?= LuaJIT-$(LUA_VERSION_BASE)$(LUA_VERSION_EXTRA) +LUA_EXECUTABLE ?= luajit-$(LUA_VERSION_BASE)$(LUA_VERSION_EXTRA) +LUA_URL ?= http://luajit.org/download/$(LUA_VERSION).tar.gz +LUA_TAR ?= $(LUA_VERSION).tar.gz +LUA_DIR ?= build/$(LUA_VERSION) +LUA_LIB ?= $(LUA_PREFIX)/lib/libluajit-5.1.a +LUA_INCLUDE ?= $(dir $(shell ls 2>/dev/null $(LUA_PREFIX)/include/luajit-$(LUA_VERSION_BASE)/lua.h || ls 2>/dev/null $(LUA_PREFIX)/include/lua.h || echo $(LUA_PREFIX)/include/luajit-$(LUA_VERSION_BASE)/lua.h)) +LUA ?= $(LUA_PREFIX)/bin/$(LUA_EXECUTABLE) + +#rule for packaging lua code into bytecode, put into a header file via genheader.lua +build/%.bc: src/%.lua $(PACKAGE_DEPS) $(LUA_LIB) + $(LUA) -b -g $< $@ +endif + +########################### +# Rules for building Terra +########################### LLVM_PREFIX = $(shell $(LLVM_CONFIG) --prefix) @@ -28,17 +132,12 @@ else CLANG_PREFIX ?= $(LLVM_PREFIX) endif -CUDA_HOME ?= /usr/local/cuda -ENABLE_CUDA ?= $(shell test -e $(CUDA_HOME) && echo 1 || echo 0) - .SUFFIXES: .SECONDARY: -UNAME := $(shell uname) - AR = ar LD = ld -FLAGS += -Wall -g -fPIC +FLAGS += -Wall -g $(PIC_FLAG) LFLAGS = -g # The -E flag is BSD-specific. It is supported (though undocumented) @@ -53,22 +152,7 @@ TERRA_VERSION_RAW=$(shell git describe --tags 2>/dev/null || echo unknown) TERRA_VERSION=$(shell echo "$(TERRA_VERSION_RAW)" | $(SED_E) 's/^release-//') FLAGS += -DTERRA_VERSION_STRING="\"$(TERRA_VERSION)\"" -# Add the following lines to Makefile.inc to switch to LuaJIT-2.1 beta releases -#LUAJIT_VERSION_BASE =2.1 -#LUAJIT_VERSION_EXTRA =.0-beta2 - -LUAJIT_VERSION_BASE ?= 2.0 -LUAJIT_VERSION_EXTRA ?= .5 -LUAJIT_VERSION ?= LuaJIT-$(LUAJIT_VERSION_BASE)$(LUAJIT_VERSION_EXTRA) -LUAJIT_EXECUTABLE ?= luajit-$(LUAJIT_VERSION_BASE)$(LUAJIT_VERSION_EXTRA) -LUAJIT_URL ?= http://luajit.org/download/$(LUAJIT_VERSION).tar.gz -LUAJIT_TAR ?= $(LUAJIT_VERSION).tar.gz -LUAJIT_DIR ?= build/$(LUAJIT_VERSION) -LUAJIT_LIB ?= $(LUAJIT_PREFIX)/lib/libluajit-5.1.a -LUAJIT_INCLUDE ?= $(dir $(shell ls 2>/dev/null $(LUAJIT_PREFIX)/include/luajit-$(LUAJIT_VERSION_BASE)/lua.h || ls 2>/dev/null $(LUAJIT_PREFIX)/include/lua.h || echo $(LUAJIT_PREFIX)/include/luajit-$(LUAJIT_VERSION_BASE)/lua.h)) -LUAJIT ?= $(LUAJIT_PREFIX)/bin/$(LUAJIT_EXECUTABLE) - -FLAGS += -I build -I $(LUAJIT_INCLUDE) -I release/include/terra -I $(shell $(LLVM_CONFIG) --includedir) -I $(CLANG_PREFIX)/include +FLAGS += -I build -I $(LUA_INCLUDE) -I release/include/terra -I $(shell $(LLVM_CONFIG) --includedir) -I $(CLANG_PREFIX)/include FLAGS += -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -O0 -fno-common -Wcast-qual CPPFLAGS = -fno-rtti -Woverloaded-virtual -fvisibility-inlines-hidden @@ -78,18 +162,30 @@ LLVM_VERSION=$(shell echo $(LLVM_VERSION_NUM) | $(SED_E) 's/^([0-9]+)\.([0-9]+). LLVMVERGT4 := $(shell expr $(LLVM_VERSION) \>= 40) FLAGS += -DLLVM_VERSION=$(LLVM_VERSION) + +#set TERRA_EXTERNAL_TERRALIB=1 to use the on-disk lua files like terralib.lua, useful for faster iteration when debugging terra itself. +ifneq ($(TERRA_EXTERNAL_TERRALIB),) +FLAGS += -DTERRA_EXTERNAL_TERRALIB="\"$(realpath src)/?.lua\"" +endif + +# customizable path locations for LuaRocks install +ifneq ($(TERRA_HOME),) +FLAGS += -DTERRA_HOME="\"$(TERRA_HOME)\"" +endif + CPPFLAGS += -std=c++11 +C99FLAGS += -std=c99 ifneq ($(findstring $(UNAME), Linux FreeBSD),) -DYNFLAGS = -shared -fPIC -TERRA_STATIC_LIBRARY += -Wl,-export-dynamic -Wl,--whole-archive $(LIBRARY) -Wl,--no-whole-archive +DYNFLAGS = -shared $(PIC_FLAG) +WHOLE_ARCHIVE += -Wl,-export-dynamic -Wl,--whole-archive $(LIBRARY) -Wl,--no-whole-archive else -DYNFLAGS = -dynamiclib -single_module -fPIC -install_name "@rpath/terra.dylib" -TERRA_STATIC_LIBRARY = -Wl,-force_load,$(LIBRARY) +DYNFLAGS = -undefined dynamic_lookup -dynamiclib -single_module $(PIC_FLAG) -install_name "@rpath/terra.so" +WHOLE_ARCHIVE = -Wl,-force_load,$(1) endif -LLVM_LIBRARY_FLAGS += $(LUAJIT_LIB) +LLVM_LIBRARY_FLAGS += $(LUA_LIB) LLVM_LIBRARY_FLAGS += $(shell $(LLVM_CONFIG) --ldflags) -L$(CLANG_PREFIX)/lib LLVM_LIBRARY_FLAGS += -lclangFrontend -lclangDriver \ -lclangSerialization -lclangCodeGen -lclangParse -lclangSema \ @@ -137,16 +233,16 @@ ifeq ($(UNAME), FreeBSD) SUPPORT_LIBRARY_FLAGS += -lexecinfo -pthread endif -PACKAGE_DEPS += $(LUAJIT_LIB) +PACKAGE_DEPS += $(LUA_LIB) #makes luajit happy on osx 10.6 (otherwise luaL_newstate returns NULL) ifeq ($(UNAME), Darwin) -LFLAGS += -pagezero_size 10000 -image_base 100000000 +LFLAGS += -pagezero_size 10000 -image_base 100000000 endif CLANG_RESOURCE_DIRECTORY=$(CLANG_PREFIX)/lib/clang/$(LLVM_VERSION_NUM) -ifeq ($(ENABLE_CUDA),1) +ifeq ($(strip $(ENABLE_CUDA)),1) CUDA_INCLUDES = -DTERRA_ENABLE_CUDA -I $(CUDA_HOME)/include -I $(CUDA_HOME)/nvvm/include FLAGS += $(CUDA_INCLUDES) endif @@ -156,32 +252,26 @@ FLAGS += -DTERRA_LLVM_HEADERS_HAVE_NDEBUG endif LIBOBJS = tkind.o tcompiler.o tllvmutil.o tcwrapper.o tinline.o terra.o lparser.o lstring.o lobject.o lzio.o llex.o lctype.o treadnumber.o tcuda.o tdebug.o tinternalizedfiles.o lj_strscan.o +ifeq ($(strip $(TERRA_USE_PUC_LUA)),1) +LIBOBJS += tffi.o tffi_ctype.o tffi_parser.o +endif LIBLUA = terralib.lua strict.lua cudalib.lua asdl.lua terralist.lua EXEOBJS = main.o linenoise.o -EMBEDDEDLUA = $(addprefix build/,$(LIBLUA:.lua=.h)) -GENERATEDHEADERS = $(EMBEDDEDLUA) build/internalizedfiles.h +EMBEDDEDLUABC = $(addprefix build/,$(LIBLUA:.lua=.bc)) +EMBEDDEDLUAH = $(addprefix build/,$(LIBLUA:.lua=.h)) +GENERATEDHEADERS = $(EMBEDDEDLUAH) build/internalizedfiles.h LUAHEADERS = lua.h lualib.h lauxlib.h luaconf.h OBJS = $(LIBOBJS) $(EXEOBJS) -EXECUTABLE = release/bin/terra LIBRARY = release/lib/libterra.a LIBRARY_NOLUA = release/lib/libterra_nolua.a LIBRARY_NOLUA_NOLLVM = release/lib/libterra_nolua_nollvm.a LIBRARY_VARIANTS = $(LIBRARY_NOLUA) $(LIBRARY_NOLUA_NOLLVM) -ifeq ($(UNAME), Darwin) -DYNLIBRARY = release/lib/terra.dylib -else -DYNLIBRARY = release/lib/terra.so -endif RELEASE_HEADERS = $(addprefix release/include/terra/,$(LUAHEADERS)) -BIN2C = build/bin2c - -#put any install-specific stuff in here --include Makefile.inc .PHONY: all clean download purge test release install all: $(EXECUTABLE) $(DYNLIBRARY) @@ -195,76 +285,84 @@ build/%.o: src/%.cpp $(PACKAGE_DEPS) $(CXX) $(FLAGS) $(CPPFLAGS) $< -c -o $@ build/%.o: src/%.c $(PACKAGE_DEPS) - $(CC) $(FLAGS) $< -c -o $@ + $(CC) $(FLAGS) $(C99FLAGS) $< -c -o $@ -download: build/$(LUAJIT_TAR) +download: build/$(LUA_TAR) -build/$(LUAJIT_TAR): -ifeq ($(UNAME), Darwin) - curl $(LUAJIT_URL) -o build/$(LUAJIT_TAR) -else - wget $(LUAJIT_URL) -O build/$(LUAJIT_TAR) -endif +build/$(LUA_TAR): + $(WGET) build/$(LUA_TAR) $(LUA_URL) -build/lib/libluajit-5.1.a: build/$(LUAJIT_TAR) - (cd build; tar -xf $(LUAJIT_TAR)) +ifeq ($(strip $(TERRA_USE_PUC_LUA)),1) +$(LUA_LIB): build/$(LUA_TAR) + (cd build; tar -xf $(LUA_TAR)) + (cd $(LUA_DIR); $(MAKE) $(LUA_TARGET) INSTALL_TOP="$(LUA_PREFIX)" CC="$(CC) $(PIC_FLAG)") + (cd $(LUA_DIR); $(MAKE) install INSTALL_TOP="$(LUA_PREFIX)" CC="$(CC) $(PIC_FLAG)") +else +$(LUA_LIB): build/$(LUA_TAR) + (cd build; tar -xf $(LUA_TAR)) # MACOSX_DEPLOYMENT_TARGET is a workaround for https://github.com/LuaJIT/LuaJIT/issues/484 - (cd $(LUAJIT_DIR); $(MAKE) install PREFIX=$(realpath build) CC=$(CC) STATIC_CC="$(CC) -fPIC" MACOSX_DEPLOYMENT_TARGET=10.6) + (cd $(LUA_DIR); $(MAKE) install PREFIX="$(LUA_PREFIX)" CC="$(CC)" STATIC_CC="$(CC) $(PIC_FLAG)" MACOSX_DEPLOYMENT_TARGET=10.6) +endif -release/include/terra/%.h: $(LUAJIT_INCLUDE)/%.h $(LUAJIT_LIB) - cp $(LUAJIT_INCLUDE)/$*.h $@ +release/include/terra/%.h: $(LUA_INCLUDE)/%.h $(LUA_LIB) + cp $(LUA_INCLUDE)/$*.h $@ build/llvm_objects/llvm_list: $(addprefix build/, $(LIBOBJS) $(EXEOBJS)) mkdir -p build/llvm_objects/luajit $(CXX) -o /dev/null $(addprefix build/, $(LIBOBJS) $(EXEOBJS)) $(LLVM_LIBRARY_FLAGS) $(SUPPORT_LIBRARY_FLAGS) $(LFLAGS) -Wl,-t 2>&1 | egrep "lib(LLVM|clang)" > build/llvm_objects/llvm_list # extract needed LLVM objects based on a dummy linker invocation - < build/llvm_objects/llvm_list $(LUAJIT) src/unpacklibraries.lua build/llvm_objects - # include all luajit objects, since the entire lua interface is used in terra + < build/llvm_objects/llvm_list $(LUA) src/unpacklibraries.lua build/llvm_objects + # include all luajit objects, since the entire lua interface is used in terra -build/lua_objects/lj_obj.o: $(LUAJIT_LIB) +build/lua_objects/lj_obj.o: $(LUA_LIB) mkdir -p build/lua_objects - cd build/lua_objects; ar x $(realpath $(LUAJIT_LIB)) + cd build/lua_objects; ar x $(realpath $(LUA_LIB)); rm -f lctype.o -$(LIBRARY): $(RELEASE_HEADERS) $(addprefix build/, $(LIBOBJS)) build/llvm_objects/llvm_list build/lua_objects/lj_obj.o +$(LIBRARY): $(RELEASE_HEADERS) $(GENERATEDHEADERS) $(addprefix build/, $(LIBOBJS)) build/llvm_objects/llvm_list build/lua_objects/lj_obj.o mkdir -p release/lib rm -f $@ $(AR) -cq $@ $(addprefix build/, $(LIBOBJS)) build/llvm_objects/*/*.o build/lua_objects/*.o ranlib $@ -$(LIBRARY_NOLUA): $(RELEASE_HEADERS) $(addprefix build/, $(LIBOBJS)) build/llvm_objects/llvm_list +$(LIBRARY_NOLUA): $(RELEASE_HEADERS) $(GENERATEDHEADERS) $(addprefix build/, $(LIBOBJS)) build/llvm_objects/llvm_list mkdir -p release/lib rm -f $@ $(AR) -cq $@ $(addprefix build/, $(LIBOBJS)) build/llvm_objects/*/*.o -$(LIBRARY_NOLUA_NOLLVM): $(RELEASE_HEADERS) $(addprefix build/, $(LIBOBJS)) +$(LIBRARY_NOLUA_NOLLVM): $(RELEASE_HEADERS) $(GENERATEDHEADERS) $(addprefix build/, $(LIBOBJS)) mkdir -p release/lib rm -f $@ $(AR) -cq $@ $(addprefix build/, $(LIBOBJS)) -$(DYNLIBRARY): $(LIBRARY) - $(CXX) $(DYNFLAGS) $(TERRA_STATIC_LIBRARY) $(SUPPORT_LIBRARY_FLAGS) -o $@ +$(DYNLIBRARY): $(LIBRARY_NOLUA) + mkdir -p release/lualib + $(CXX) $(DYNFLAGS) $(call WHOLE_ARCHIVE,$(LIBRARY_NOLUA)) $(SUPPORT_LIBRARY_FLAGS) -o $@ + +ifeq ($(TERRA_EXTERNAL_LUA),) +LUA_AND_TERRA=$(call WHOLE_ARCHIVE,$(LIBRARY)) +EXECUTABLE_LIBRARY_DEPENDENCY=$(LIBRARY) +else +LUA_AND_TERRA=$(LUA_LIB) $(call WHOLE_ARCHIVE,$(LIBRARY_NOLUA)) +EXECUTABLE_LIBRARY_DEPENDENCY=$(LIBRARY_NOLUA) +endif -$(EXECUTABLE): $(addprefix build/, $(EXEOBJS)) $(LIBRARY) +$(EXECUTABLE): $(addprefix build/, $(EXEOBJS)) $(EXECUTABLE_LIBRARY_DEPENDENCY) mkdir -p release/bin release/lib - $(CXX) $(addprefix build/, $(EXEOBJS)) -o $@ $(LFLAGS) $(TERRA_STATIC_LIBRARY) $(SUPPORT_LIBRARY_FLAGS) + $(CXX) $(addprefix build/, $(EXEOBJS)) -o $@ $(LFLAGS) $(LUA_AND_TERRA) $(SUPPORT_LIBRARY_FLAGS) $(TERRA_RPATH_FLAGS) if [ ! -e terra ]; then ln -s $(EXECUTABLE) terra; fi; -$(BIN2C): src/bin2c.c - $(CC) -O3 -o $@ $< - - #rule for packaging lua code into a header file -build/%.bc: src/%.lua $(PACKAGE_DEPS) - $(LUAJIT) -bg $< $@ -build/%.h: build/%.bc $(PACKAGE_DEPS) - $(LUAJIT) src/genheader.lua $< $@ +# fix narrowing warnings by using unsigned char +build/%.h: build/%.bc $(PACKAGE_DEPS) src/genheader.lua + $(LUA) src/genheader.lua $< $@ -build/internalizedfiles.h: $(PACKAGE_DEPS) src/geninternalizedfiles.lua lib/std.t lib/parsing.t - $(LUAJIT) src/geninternalizedfiles.lua $@ $(CLANG_RESOURCE_DIRECTORY) "%.h$$" $(CLANG_RESOURCE_DIRECTORY) "%.modulemap$$" lib "%.t$$" +TERRA_LIBRARY_FILES=lib/std.t lib/parsing.t +build/internalizedfiles.h: $(PACKAGE_DEPS) src/geninternalizedfiles.lua $(TERRA_LIBRARY_FILES) $(EMBEDDEDLUAH) + $(LUA) src/geninternalizedfiles.lua POSIX $@ $(CLANG_RESOURCE_DIRECTORY) "%.h$$" $(CLANG_RESOURCE_DIRECTORY) "%.modulemap$$" lib "%.t$$" clean: - rm -rf build/*.o build/*.d $(GENERATEDHEADERS) + rm -rf build/*.o build/*.d $(EMBEDDEDLUABC) $(GENERATEDHEADERS) rm -rf $(EXECUTABLE) terra $(LIBRARY) $(LIBRARY_NOLUA) $(LIBRARY_NOLUA_NOLLVM) $(DYNLIBRARY) $(RELEASE_HEADERS) build/llvm_objects build/lua_objects purge: clean @@ -279,9 +377,12 @@ release: zip -q -r $(RELEASE_NAME).zip $(RELEASE_NAME) mv $(RELEASE_NAME) release -PREFIX ?= /usr/local install: all - cp -R release/* $(PREFIX) + cp -R release/bin/* $(INSTALL_BINARY_DIR) + cp -R release/lib/* $(INSTALL_LIBRARY_DIR) + cp -R release/share/* $(INSTALL_SHARE_DIR) + cp -R release/include/* $(INSTALL_INCLUDE_DIR) + cp -R release/lualib/* $(INSTALL_LUA_LIBRARY_DIR) # dependency rules DEPENDENCIES = $(patsubst %.o,build/%.d,$(OBJS)) diff --git a/src/asdl.lua b/src/asdl.lua index f913f92f..9f7bd43a 100644 --- a/src/asdl.lua +++ b/src/asdl.lua @@ -1,3 +1,22 @@ +local function isluajit() + return type(rawget(_G,"jit")) == "table" +end + +local iscdata +if isluajit() then + function iscdata(v) + return type(v) == "cdata" + end +else + local ffi = require("ffi") + local function hastype(v) + return ffi.typeof(v) + end + function iscdata(v) + return type(v) == "userdata" and pcall(hastype, v) + end +end + local List = require("terralist") local Context = {} @@ -163,7 +182,11 @@ local function parseAll(text) end local function checkbuiltin(t) - return function(v) return type(v) == t end + if t == "cdata" then + return function(v) return iscdata(v) end + else + return function(v) return type(v) == t end + end end local function checkoptional(checkt) @@ -398,4 +421,4 @@ function Context:Define(text) end end end -package.loaded["asdl"] = { NewContext = NewContext, List = List } \ No newline at end of file +package.loaded["asdl"] = { NewContext = NewContext, List = List, isluajit = isluajit, iscdata = iscdata } diff --git a/src/cudalib.lua b/src/cudalib.lua index 75ee97fc..b7c7799f 100644 --- a/src/cudalib.lua +++ b/src/cudalib.lua @@ -109,6 +109,7 @@ cudalib.useculink = false -- this this is only needed for cuda compilation, we load this library lazily below local terracode = [[ local ffi = require('ffi') +local asdl = require('asdl') local ef = terralib.externfunction local struct CUctx_st local struct CUfunc_st @@ -347,7 +348,11 @@ local function dumpsass(data,sz) local nvdisasm = terralib.cudahome..(ffi.os == "Windows" and "\\bin\\nvdisasm.exe" or "/bin/nvdisasm") os.execute(string.format("%q --print-life-ranges dump.sass",nvdisasm)) end -dumpsass = terralib.cast({&opaque,uint64} -> {},dumpsass) +if asdl.isluajit() then + dumpsass = terralib.cast({&opaque,uint64} -> {},dumpsass) +else + dumpsass = nil -- FIXME: PUC Lua doesn't support callbacks +end function cudalib.compile(module,dumpmodule,version,jitload) version = version or cudalib.localversion() diff --git a/src/geninternalizedfiles.lua b/src/geninternalizedfiles.lua index 9a17df52..8f734c14 100644 --- a/src/geninternalizedfiles.lua +++ b/src/geninternalizedfiles.lua @@ -1,10 +1,9 @@ -local outputfilename = arg[1] +local os,outputfilename = arg[1],arg[2] -local ffi = require("ffi") -local findcmd = ffi.os == "Windows" and "cmd /c dir /b /s \"%s\"" or "find %q" +local findcmd = os == "Windows" and "cmd /c dir /b /s \"%s\"" or "find %q" local listoffiles = {} -for i = 2,#arg,2 do +for i = 3,#arg,2 do local path,pattern = arg[i],arg[i+1] local p = assert(io.popen(findcmd:format(path))) print(findcmd:format(path)) @@ -66,4 +65,4 @@ table.insert(output,RegisterTemplate:format(table.concat(names,","),table.concat local outputfile = io.open(outputfilename,"w") outputfile:write(table.concat(output)) -outputfile:close() \ No newline at end of file +outputfile:close() diff --git a/src/main.cpp b/src/main.cpp index fbe870ad..b77717e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -73,10 +73,19 @@ static int luapanic( exit(1); } +#ifdef TERRA_USE_PUC_LUA +extern "C" { +void preload_ffi(lua_State *L); +} +#endif + int main(int argc, char **argv) { progname = argv[0]; lua_State *L = luaL_newstate(); luaL_openlibs(L); +#ifdef TERRA_USE_PUC_LUA + preload_ffi(L); +#endif lua_atpanic(L, luapanic); terra_Options options; memset(&options, 0, sizeof(terra_Options)); diff --git a/src/tcompiler.cpp b/src/tcompiler.cpp index e4693b3a..ebbaae02 100644 --- a/src/tcompiler.cpp +++ b/src/tcompiler.cpp @@ -42,9 +42,22 @@ extern "C" { using namespace llvm; +#ifdef TERRA_USE_PUC_LUA +#include "tffi.h" +#endif + +static const void *tocdata(lua_State *L, int idx) { +#ifdef TERRA_USE_PUC_LUA + // libffi uses a 32 byte header for each cdata object, + // which we have to skip here + return ((char *)lua_topointer(L, idx) + sizeof(struct cdata)); +#else + return lua_topointer(L, idx); +#endif +} + #define TERRALIB_FUNCTIONS(_) \ _(inittarget, 1) \ - _(freetarget, 0) \ _(initcompilationunit, 1) \ _(compilationunitaddvalue, \ 1) /*entry point from lua into compiler to generate LLVM for a function, other \ @@ -294,6 +307,17 @@ bool HostHasAVX() { return Features["avx"]; } +static inline void terra_pushpointerwithgc(lua_State *L, void *ptr, + lua_CFunction destructor) { + void **blockaddr = (void **)lua_newuserdata(L, sizeof(void *)); + *blockaddr = ptr; + lua_newtable(L); + lua_pushcfunction(L, destructor); + lua_setfield(L, -2, "__gc"); + lua_setmetatable(L, -2); +} + +int terra_freetarget(lua_State *L); int terra_inittarget(lua_State *L) { terra_State *T = terra_getstate(L, 1); TerraTarget *TT = new TerraTarget(); @@ -373,14 +397,15 @@ int terra_inittarget(lua_State *L) { CodeGenOpt::Aggressive); TT->external = new Module("external", *TT->ctx); TT->external->setTargetTriple(TT->Triple); - lua_pushlightuserdata(L, TT); + terra_pushpointerwithgc(L, TT, terra_freetarget); return 1; } +int terra_freecompilationunit(lua_State *L); int terra_initcompilationunit(lua_State *L) { terra_State *T = terra_getstate(L, 1); TerraCompilationUnit *CU = new TerraCompilationUnit(); - TerraTarget *TT = (TerraTarget *)terra_tocdatapointer(L, 1); + TerraTarget *TT = terra_totarget(L, 1); CU->TT = TT; CU->TT->nreferences++; CU->nreferences = 1; @@ -404,7 +429,7 @@ int terra_initcompilationunit(lua_State *L) { llvmutil_addtargetspecificpasses(CU->fpm, TT->tm); llvmutil_addoptimizationpasses(CU->fpm); CU->fpm->doInitialization(); - lua_pushlightuserdata(L, CU); + terra_pushpointerwithgc(L, CU, terra_freecompilationunit); return 1; } @@ -487,7 +512,7 @@ void freetarget(TerraTarget *TT) { } } int terra_freetarget(lua_State *L) { - freetarget((TerraTarget *)terra_tocdatapointer(L, 1)); + freetarget(terra_totarget(L, 1)); return 0; } @@ -509,7 +534,7 @@ static void freecompilationunit(TerraCompilationUnit *CU) { } } int terra_freecompilationunit(lua_State *L) { - freecompilationunit((TerraCompilationUnit *)terra_tocdatapointer(L, 1)); + freecompilationunit(terra_tocompilationunit(L, 1)); return 0; } @@ -2161,7 +2186,7 @@ struct FunctionEmitter { TType *typ = typeOfValue(exp); Obj value; exp->pushfield("value"); - const void *data = lua_topointer(L, -1); + const void *data = tocdata(L, -1); assert(data); size_t size = CU->getDataLayout().getTypeAllocSize(typ->type); Value *r; @@ -2843,8 +2868,10 @@ static int terra_compilationunitaddvalue( const char *modulename = (lua_isnil(L, 2)) ? NULL : lua_tostring(L, 2); lua_pushvalue(L, 3); // the function definition cu.fromStack(&value); - TerraCompilationUnit *CU = (TerraCompilationUnit *)cu.cd("llvm_cu"); + cu.pushfield("llvm_cu"); + TerraCompilationUnit *CU = terra_tocompilationunit(L, -1); assert(CU); + lua_pop(L, 1); Types Ty(CU); CCallingConv CC(CU, &Ty); @@ -2881,11 +2908,10 @@ static int terra_compilationunitaddvalue( return 1; } -static int terra_llvmsizeof(lua_State *L) { +static void RetrieveCUAndType(lua_State *L, TerraCompilationUnit **CUp, + TType **llvmtypp) { terra_State *T = terra_getstate(L, 1); int ref_table = lobj_newreftable(L); - TType *llvmtyp; - TerraCompilationUnit *CU; { Obj cu, typ, globals; lua_pushvalue(L, 1); @@ -2893,13 +2919,21 @@ static int terra_llvmsizeof(lua_State *L) { lua_pushvalue(L, 2); typ.initFromStack(L, ref_table); cu.obj("symbols", &globals); - CU = (TerraCompilationUnit *)cu.cd("llvm_cu"); - CU->symbols = &globals; - llvmtyp = Types(CU).Get(&typ); - CU->symbols = NULL; + cu.pushfield("llvm_cu"); + (*CUp) = terra_tocompilationunit(L, -1); + lua_pop(L, 1); + (*CUp)->symbols = &globals; + *llvmtypp = Types(*CUp).Get(&typ); + (*CUp)->symbols = NULL; } lobj_removereftable(T->L, ref_table); - lua_pushnumber(T->L, CU->getDataLayout().getTypeAllocSize(llvmtyp->type)); +} + +static int terra_llvmsizeof(lua_State *L) { + TerraCompilationUnit *CU; + TType *llvmtyp; + RetrieveCUAndType(L, &CU, &llvmtyp); + lua_pushnumber(L, CU->getDataLayout().getTypeAllocSize(llvmtyp->type)); return 1; } @@ -2962,7 +2996,7 @@ static void *JITGlobalValue(TerraCompilationUnit *CU, GlobalValue *gv) { static int terra_jit(lua_State *L) { terra_getstate(L, 1); - TerraCompilationUnit *CU = (TerraCompilationUnit *)terra_tocdatapointer(L, 1); + TerraCompilationUnit *CU = terra_tocompilationunit(L, 1); GlobalValue *gv = (GlobalValue *)lua_touserdata(L, 2); double begin = CurrentTimeInSeconds(); void *ptr = JITGlobalValue(CU, gv); @@ -2973,8 +3007,7 @@ static int terra_jit(lua_State *L) { } static int terra_deletefunction(lua_State *L) { - TerraCompilationUnit *CU = - (TerraCompilationUnit *)terra_tocdatapointer(L, lua_upvalueindex(1)); + TerraCompilationUnit *CU = terra_tocompilationunit(L, lua_upvalueindex(1)); TerraFunctionState *fstate = (TerraFunctionState *)lua_touserdata(L, -1); assert(fstate); Function *func = fstate->func; @@ -3149,7 +3182,7 @@ static int terra_saveobjimpl(lua_State *L) { bool optimize = lua_toboolean(L, 5); lua_getfield(L, 3, "llvm_cu"); - TerraCompilationUnit *CU = (TerraCompilationUnit *)terra_tocdatapointer(L, -1); + TerraCompilationUnit *CU = terra_tocompilationunit(L, -1); assert(CU); if (optimize) { llvmutil_optimizemodule(CU->M, CU->TT->tm); @@ -3198,15 +3231,21 @@ static int terra_saveobjimpl(lua_State *L) { } static int terra_pointertolightuserdata(lua_State *L) { - lua_pushlightuserdata(L, terra_tocdatapointer(L, -1)); + if (10 != lua_type(L, 1)) + return 0; // not a cdata, 10 is from LuaJIT sources since it is not exposed in + // the normal API + void *const *cdata = (void *const *)lua_topointer(L, 1); + if (!cdata) return 0; + lua_pushlightuserdata(L, *cdata); return 1; } + static int terra_bindtoluaapi(lua_State *L) { int N = lua_gettop(L); assert(N >= 1); - void *const *fn = (void *const *)lua_topointer(L, 1); + void *fn = lua_touserdata(L, 1); assert(fn); - lua_pushcclosure(L, (lua_CFunction)*fn, N - 1); + lua_pushcclosure(L, (lua_CFunction)fn, N - 1); return 1; } #if _MSC_VER < 1900 && defined(_WIN32) @@ -3232,7 +3271,7 @@ static int terra_linkllvmimpl(lua_State *L) { terra_State *T = terra_getstate(L, 1); (void)T; std::string Err; - TerraTarget *TT = (TerraTarget *)terra_tocdatapointer(L, 1); + TerraTarget *TT = terra_totarget(L, 1); size_t length; const char *filename = lua_tolstring(L, 2, &length); bool fromstring = lua_toboolean(L, 3); @@ -3318,7 +3357,7 @@ static int terra_linkllvmimpl(lua_State *L) { static int terra_dumpmodule(lua_State *L) { terra_State *T = terra_getstate(L, 1); (void)T; - TerraCompilationUnit *CU = (TerraCompilationUnit *)terra_tocdatapointer(L, 1); + TerraCompilationUnit *CU = terra_tocompilationunit(L, 1); if (CU) TERRA_DUMP_MODULE(CU->M); return 0; } diff --git a/src/tcompilerstate.h b/src/tcompilerstate.h index 13f75716..b723b088 100644 --- a/src/tcompilerstate.h +++ b/src/tcompilerstate.h @@ -85,4 +85,15 @@ struct terra_CompilerState { llvm::DenseMap functioninfo; }; -#endif \ No newline at end of file +static inline TerraCompilationUnit *terra_tocompilationunit(lua_State *L, int idx) { + void *blockaddr = lua_touserdata(L, idx); + if (!blockaddr) return NULL; + return *(TerraCompilationUnit **)blockaddr; +} +static inline TerraTarget *terra_totarget(lua_State *L, int idx) { + void *blockaddr = lua_touserdata(L, idx); + if (!blockaddr) return NULL; + return *(TerraTarget **)blockaddr; +} + +#endif diff --git a/src/tcuda.cpp b/src/tcuda.cpp index d2095202..fbd0f045 100644 --- a/src/tcuda.cpp +++ b/src/tcuda.cpp @@ -267,7 +267,7 @@ int terra_toptx(lua_State *L) { terra_State *T = terra_getstate(L, 1); initializeNVVMState(T); lua_getfield(L, 1, "llvm_cu"); - TerraCompilationUnit *CU = (TerraCompilationUnit *)terra_tocdatapointer(L, -1); + TerraCompilationUnit *CU = terra_tocompilationunit(L, -1); llvm::Module *M = CU->M; int annotations = 2; int dumpmodule = lua_toboolean(L, 3); diff --git a/src/tcuda.h b/src/tcuda.h index b95c283e..2933431d 100644 --- a/src/tcuda.h +++ b/src/tcuda.h @@ -5,4 +5,4 @@ struct terra_State; int terra_cudainit(struct terra_State* T); int terra_cudafree(struct terra_State* T); -#endif \ No newline at end of file +#endif diff --git a/src/tcwrapper.cpp b/src/tcwrapper.cpp index 52eb19bf..78ee07b0 100644 --- a/src/tcwrapper.cpp +++ b/src/tcwrapper.cpp @@ -68,7 +68,7 @@ class IncludeCVisitor : public RecursiveASTVisitor { } void PushTypeField(const char *name) { - lua_getfield(L, LUA_GLOBALSINDEX, "terra"); + lua_getglobal(L, "terra"); lua_getfield(L, -1, "types"); lua_getfield(L, -1, name); lua_remove(L, -2); @@ -482,7 +482,7 @@ class IncludeCVisitor : public RecursiveASTVisitor { void CreateFunction(const std::string &name, const std::string &internalname, Obj *typ) { if (!general.hasfield(name.c_str())) { - lua_getfield(L, LUA_GLOBALSINDEX, "terra"); + lua_getglobal(L, "terra"); lua_getfield(L, -1, "externfunction"); lua_remove(L, -2); // terra table lua_pushstring(L, internalname.c_str()); @@ -538,7 +538,7 @@ class IncludeCVisitor : public RecursiveASTVisitor { } void CreateExternGlobal(const std::string &name, Obj *typ) { if (!general.hasfield(name.c_str())) { - lua_getfield(L, LUA_GLOBALSINDEX, "terra"); + lua_getglobal(L, "terra"); lua_getfield(L, -1, "global"); lua_remove(L, -2); // terra table typ->push(); @@ -1148,7 +1148,7 @@ int include_c(lua_State *L) { terra_State *T = terra_getstate(L, 1); (void)T; lua_getfield(L, TARGET_POS, "llvm_target"); - TerraTarget *TT = (TerraTarget *)terra_tocdatapointer(L, -1); + TerraTarget *TT = terra_totarget(L, -1); const char *code = luaL_checkstring(L, 2); int N = lua_objlen(L, 3); std::vector args; @@ -1195,11 +1195,11 @@ int include_c(lua_State *L) { } void terra_cwrapperinit(terra_State *T) { - lua_getfield(T->L, LUA_GLOBALSINDEX, "terra"); + lua_getglobal(T->L, "terra"); lua_pushlightuserdata(T->L, (void *)T); lua_pushcclosure(T->L, include_c, 1); lua_setfield(T->L, -2, "registercfile"); - lua_pop(T->L, -1); // terra object + lua_pop(T->L, 1); // terra object } diff --git a/src/tdebug.cpp b/src/tdebug.cpp index beb1f1fe..dd19d0bc 100644 --- a/src/tdebug.cpp +++ b/src/tdebug.cpp @@ -3,7 +3,7 @@ #include "terrastate.h" #include "tcompilerstate.h" -#if !defined(__arm__) && !defined(__aarch64__) +#if !defined(__arm__) && !defined(__aarch64__) && !defined(__PPC__) #ifndef _WIN32 #include @@ -295,7 +295,7 @@ int terra_debuginit(struct terra_State *T) { void *lookupline = createclosure((uint8_t *)T->C->MB.base() + 2 * CLOSURE_MAX_SIZE, (void *)terra_lookupline, 4, (void **)&T->C, 1); - lua_getfield(T->L, LUA_GLOBALSINDEX, "terra"); + lua_getglobal(T->L, "terra"); lua_getfield(T->L, -1, "initdebugfns"); lua_pushlightuserdata(T->L, (void *)stacktracefn); lua_pushlightuserdata(T->L, (void *)terra_backtrace); diff --git a/src/terra.cpp b/src/terra.cpp index e7db8add..18fe0908 100644 --- a/src/terra.cpp +++ b/src/terra.cpp @@ -424,8 +424,8 @@ int terra_initwithoptions(lua_State *L, terra_Options *options) { lua_setfield(L, -2, "__terrastate"); // reference to our T object, so that we can // load it from the lua state on other API calls - lua_setfield(T->L, LUA_GLOBALSINDEX, "terra"); // create global terra object - terra_kindsinit(T); // initialize lua mapping from T_Kind to/from string + lua_setglobal(T->L, "terra"); // create global terra object + terra_kindsinit(T); // initialize lua mapping from T_Kind to/from string setterrahome(T->L); // find the location of support files such as the clang resource // directory @@ -466,7 +466,7 @@ int terra_initwithoptions(lua_State *L, terra_Options *options) { terra_cwrapperinit(T); - lua_getfield(T->L, LUA_GLOBALSINDEX, "terra"); + lua_getglobal(T->L, "terra"); lua_pushcfunction(T->L, terra_luaload); lua_setfield(T->L, -2, "load"); diff --git a/src/terralib.lua b/src/terralib.lua index 54fe8cd6..333b49ac 100644 --- a/src/terralib.lua +++ b/src/terralib.lua @@ -3,6 +3,9 @@ local ffi = require("ffi") local asdl = require("asdl") local List = asdl.List +local isluajit = asdl.isluajit +local iscdata = asdl.iscdata + -- LINE COVERAGE INFORMATION, must run test script with luajit and not terra to avoid overwriting coverage with old version if false then local converageloader = loadfile("coverageinfo.lua") @@ -422,7 +425,11 @@ local function invokeuserfunction(anchor, what, speculate, userfn, ...) -- invokeuserfunction is recognized by a customtraceback and we need to prevent the tail call return result end - local success,result = xpcall(userfn,debug.traceback,...) + local userargs = {...} + local function userwrapper() + return userfn(unpack(userargs)) + end + local success,result = xpcall(userwrapper,debug.traceback) -- same here return success, result end @@ -726,12 +733,6 @@ local function newweakkeytable() return setmetatable({},weakkeys) end -local function cdatawithdestructor(ud,dest) - local cd = ffi.cast("void*",ud) - ffi.gc(cd,dest) - return cd -end - terra.target = {} terra.target.__index = terra.target function terra.istarget(a) return getmetatable(a) == terra.target end @@ -742,7 +743,7 @@ function terra.newtarget(tbl) CPU = CPU or "" Features = Features or "" end - return setmetatable({ llvm_target = cdatawithdestructor(terra.inittarget(Triple,CPU,Features,FloatABIHard),terra.freetarget), + return setmetatable({ llvm_target = terra.inittarget(Triple,CPU,Features,FloatABIHard), Triple = Triple, cnametostruct = { general = {}, tagged = {}} --map from llvm_name -> terra type used to make c structs unique per llvm_name },terra.target) @@ -768,7 +769,7 @@ function terra.newcompilationunit(target,opt) assert(terra.istarget(target),"expected a target object") return setmetatable({ symbols = newweakkeytable(), collectfunctions = opt, - llvm_cu = cdatawithdestructor(terra.initcompilationunit(target.llvm_target,opt),terra.freecompilationunit) },compilationunit) -- mapping from Types,Functions,Globals,Constants -> llvm value associated with them for this compilation + llvm_cu = terra.initcompilationunit(target.llvm_target,opt) },compilationunit) -- mapping from Types,Functions,Globals,Constants -> llvm value associated with them for this compilation end function compilationunit:addvalue(k,v) if type(k) ~= "string" then k,v = nil,k end @@ -781,7 +782,7 @@ function compilationunit:jitvalue(v) end function compilationunit:free() assert(not self.collectfunctions, "cannot explicitly release a compilation unit with auto-delete functions") - ffi.gc(self.llvm_cu,nil) --unregister normal destructor object + getmetatable(self.llvm_cu).__gc = nil terra.freecompilationunit(self.llvm_cu) end function compilationunit:dump() terra.dumpmodule(self.llvm_cu) end @@ -1450,10 +1451,15 @@ do --create a map from this ctype to the terra type to that we can implement terra.typeof(cdata) local ctype = ffi.typeof(self.cachedcstring) - types.ctypetoterra[tonumber(ctype)] = self - local rctype = ffi.typeof(self.cachedcstring.."&") - types.ctypetoterra[tonumber(rctype)] = self - + -- tonumber(ctype) is LuaJIT-only, so rely on printed representation in PUC Lua + local ctype_key = tonumber(ctype) or tostring(ctype) + types.ctypetoterra[ctype_key] = self + if isluajit() then + local rctype = ffi.typeof(self.cachedcstring.."&") + local rctype_key = tonumber(rctype) or tostring(rctype) + types.ctypetoterra[rctype_key] = self + end + if self:isstruct() then local function index(obj,idx) local method = self:getmethod(idx) @@ -1674,12 +1680,21 @@ do name = "u"..name end local min,max + -- Hack: The syntax below doesn't parse in Lua 5.1-5.2, so + -- wrapping it in a conditional eval is the only way to + -- guard against parse errors. + local function eval(program) + local obj = loadstring(program) + if obj then + return obj() + end + end if not s then - min = 0ULL - max = -1ULL + min = eval("return 0ULL") + max = eval("return -1ULL") else - min = 2LL ^ (bits - 1) - max = min - 1 + min = eval("return 2LL ^ (" .. tostring(bits) .. " - 1)") + max = min and min - 1 end local typ = T.primitive("integer",size,s) globaltype(name,typ,min,max) @@ -2111,7 +2126,7 @@ function typecheck(topexp,luaenv,simultaneousdefinitions) elseif terra.istree(v) then --if this is a raw tree, we just drop it in place and hope the user knew what they were doing return v - elseif type(v) == "cdata" then + elseif iscdata(v) then local typ = terra.typeof(v) if typ:isaggregate() then --when an aggregate is directly referenced from Terra we get its pointer --a constant would make an entire copy of the object @@ -3351,22 +3366,17 @@ local function fileparts(path) local pattern = "[%s]([^%s]*)" return path:gmatch(pattern:format(fileseparators,fileseparators)) end -function terra.registerinternalizedfiles(names,contents,sizes) - names,contents,sizes = ffi.cast("const char **",names),ffi.cast("uint8_t **",contents),ffi.cast("int*",sizes) - for i = 0,math.huge do - if names[i] == nil then break end - local name,content,size = ffi.string(names[i]),contents[i],sizes[i] - local cur = internalizedfiles - for segment in fileparts(name) do - cur.children = cur.children or {} - cur.kind = "directory" - if not cur.children[segment] then - cur.children[segment] = {} - end - cur = cur.children[segment] +function terra.registerinternalizedfile(name,content,size) + local cur = internalizedfiles + for segment in fileparts(name) do + cur.children = cur.children or {} + cur.kind = "directory" + if not cur.children[segment] then + cur.children[segment] = {} end - cur.contents,cur.size,cur.kind = terra.pointertolightuserdata(content), size, "file" + cur = cur.children[segment] end + cur.contents,cur.size,cur.kind = content, size, "file" end local function getinternalizedfile(path) @@ -3484,8 +3494,8 @@ local function createunpacks(tupleonly) return result end local function unpacklua(cdata,from,to) - local t = type(cdata) == "cdata" and terra.typeof(cdata) - if not t or not t:isstruct() or (tupleonly and t.convertible ~= "tuple") then + local t = iscdata(cdata) and terra.typeof(cdata) + if not t or not t:isstruct() or (tupleonly and t.convertible ~= "tuple") then return cdata end local results = terralib.newlist() @@ -4268,7 +4278,7 @@ function terra.constant(typ,init) typ,init = nil,typ end if typ == nil then --try to infer the type, and if successful build the constant - if type(init) == "cdata" then + if iscdata(init) then typ = terra.typeof(init) elseif type(init) == "number" then typ = (terra.isintegral(init) and terra.types.int) or terra.types.double @@ -4290,7 +4300,7 @@ function terra.constant(typ,init) return terra.newquote(newobject(anchor,T.literal,init,typ)) end local orig = init -- hold anchor until we capture the value - if type(init) ~= "cdata" or terra.typeof(init) ~= typ then + if not iscdata(init) or terra.typeof(init) ~= typ then init = terra.cast(typ,init) end if not typ:isaggregate() then @@ -4311,10 +4321,10 @@ _G["constant"] = terra.constant -- equivalent to ffi.typeof, takes a cdata object and returns associated terra type object function terra.typeof(obj) - if type(obj) ~= "cdata" then + if not iscdata(obj) then error("cannot get the type of a non cdata object") end - return terra.types.ctypetoterra[tonumber(ffi.typeof(obj))] + return terra.types.ctypetoterra[tostring(ffi.typeof(obj))] end --equivalent to Lua's type function, but knows about concepts in Terra to improve error reporting diff --git a/src/terrastate.h b/src/terrastate.h index ee770d23..d9f5971a 100644 --- a/src/terrastate.h +++ b/src/terrastate.h @@ -42,7 +42,7 @@ terra_State *terra_getstate(lua_State *L, int closureindex); #define VERBOSE_ONLY(T) if ((T)->options.verbose != 0) #define DEBUG_ONLY(T) if ((T)->options.debug != 0) -// definition in tclanginternalizedheaders.cpp +// definition in tinternalizedfiles.cpp void terra_registerinternalizedfiles(lua_State *L, int terratable); #endif diff --git a/src/tffi.c b/src/tffi.c new file mode 100644 index 00000000..933c8899 --- /dev/null +++ b/src/tffi.c @@ -0,0 +1,2994 @@ +/* vim: ts=4 sw=4 sts=4 et tw=78 + * Copyright (c) 2011 James R. McKaskill. See license in tffi.h + */ +#include "tffi.h" +#include +#include + +/* Set to 1 to get extra debugging on print */ +#define DEBUG_TOSTRING 0 + +int jit_key; +int ctype_mt_key; +int cdata_mt_key; +int callback_mt_key; +int cmodule_mt_key; +int constants_key; +int types_key; +int gc_key; +int callbacks_key; +int functions_key; +int abi_key; +int next_unnamed_key; +int niluv_key; +int asmname_key; + +#if LUA_VERSION_NUM == 501 +/* +** set functions from list 'l' into table at top - 'nup'; each +** function gets the 'nup' elements at the top as upvalues. +** Returns with only the table at the stack. +*/ +static void luaL_setfuncs(lua_State* L, const luaL_Reg* l, int nup) { + luaL_checkstack(L, nup, "too many upvalues"); + for (; l && l->name; l++) { /* fill the table with given functions */ + int i; + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -nup); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + lua_setfield(L, -(nup + 2), l->name); + } + lua_pop(L, nup); /* remove upvalues */ +} +#endif + +void push_upval(lua_State* L, int* key) { + lua_pushlightuserdata(L, key); + lua_rawget(L, LUA_REGISTRYINDEX); +} + +void set_upval(lua_State* L, int* key) { + lua_pushlightuserdata(L, key); + lua_insert(L, -2); + lua_rawset(L, LUA_REGISTRYINDEX); +} + +int equals_upval(lua_State* L, int idx, int* key) { + int ret; + lua_pushvalue(L, idx); + push_upval(L, key); + ret = lua_rawequal(L, -2, -1); + lua_pop(L, 2); + return ret; +} + +static int type_error(lua_State* L, int idx, const char* to_type, int to_usr, + const struct ctype* to_ct) { + luaL_Buffer B; + struct ctype ft; + + assert(to_type || (to_usr && to_ct)); + if (to_usr) { + to_usr = lua_absindex(L, to_usr); + } + + idx = lua_absindex(L, idx); + + luaL_buffinit(L, &B); + to_cdata(L, idx, &ft); + + if (ft.type != INVALID_TYPE) { + push_type_name(L, -1, &ft); + lua_pushfstring(L, "unable to convert argument %d from cdata<%s> to cdata<", idx, + lua_tostring(L, -1)); + lua_remove(L, -2); + luaL_addvalue(&B); + } else { + lua_pushfstring(L, "unable to convert argument %d from lua<%s> to cdata<", idx, + luaL_typename(L, idx)); + luaL_addvalue(&B); + } + + if (to_ct) { + push_type_name(L, to_usr, to_ct); + luaL_addvalue(&B); + } else { + luaL_addstring(&B, to_type); + } + + luaL_addchar(&B, '>'); + + luaL_pushresult(&B); + return lua_error(L); +} + +static int64_t check_intptr(lua_State* L, int idx, void* p, struct ctype* ct) { + if (ct->type == INVALID_TYPE) { + int64_t ret; + memset(ct, 0, sizeof(*ct)); + ct->base_size = 8; + ct->type = INT64_TYPE; + ct->is_defined = 1; + ret = luaL_checknumber(L, idx); + return ret; + + } else if (ct->pointers) { + return (intptr_t)p; + } + + switch (ct->type) { + case INTPTR_TYPE: + case FUNCTION_PTR_TYPE: + return *(intptr_t*)p; + + case INT64_TYPE: + return *(int64_t*)p; + + case INT32_TYPE: + return ct->is_unsigned ? (int64_t) * (uint32_t*)p : (int64_t) * (int32_t*)p; + + case INT16_TYPE: + return ct->is_unsigned ? (int64_t) * (uint16_t*)p : (int64_t) * (int16_t*)p; + + case INT8_TYPE: + return ct->is_unsigned ? (int64_t) * (uint8_t*)p : (int64_t) * (int8_t*)p; + + default: + type_error(L, idx, "intptr_t", 0, NULL); + return 0; + } +} + +#define TO_NUMBER(TYPE, ALLOW_POINTERS) \ + TYPE real = 0, imag = 0; \ + void* p; \ + struct ctype ct; \ + \ + switch (lua_type(L, idx)) { \ + case LUA_TBOOLEAN: \ + real = (TYPE)lua_toboolean(L, idx); \ + break; \ + \ + case LUA_TNUMBER: \ + real = (TYPE)lua_tonumber(L, idx); \ + break; \ + \ + case LUA_TSTRING: \ + if (!ALLOW_POINTERS) { \ + type_error(L, idx, #TYPE, 0, NULL); \ + } \ + real = (TYPE)(intptr_t)lua_tostring(L, idx); \ + break; \ + \ + case LUA_TLIGHTUSERDATA: \ + if (!ALLOW_POINTERS) { \ + type_error(L, idx, #TYPE, 0, NULL); \ + } \ + real = (TYPE)(intptr_t)lua_topointer(L, idx); \ + break; \ + \ + case LUA_TUSERDATA: \ + p = to_cdata(L, idx, &ct); \ + \ + if (ct.type == INVALID_TYPE) { \ + if (!ALLOW_POINTERS) { \ + type_error(L, idx, #TYPE, 0, NULL); \ + } \ + real = (TYPE)(intptr_t)p; \ + } else if (ct.pointers || ct.type == STRUCT_TYPE || ct.type == UNION_TYPE) { \ + if (!ALLOW_POINTERS) { \ + type_error(L, idx, #TYPE, 0, NULL); \ + } \ + real = (TYPE)(intptr_t)p; \ + } else if (ct.type == COMPLEX_DOUBLE_TYPE) { \ + real = (TYPE)creal(*(complex_double*)p); \ + imag = (TYPE)cimag(*(complex_double*)p); \ + } else if (ct.type == COMPLEX_FLOAT_TYPE) { \ + real = (TYPE)crealf(*(complex_float*)p); \ + imag = (TYPE)cimagf(*(complex_float*)p); \ + } else if (ct.type == DOUBLE_TYPE) { \ + real = (TYPE) * (double*)p; \ + } else if (ct.type == FLOAT_TYPE) { \ + real = (TYPE) * (float*)p; \ + } else { \ + real = check_intptr(L, idx, p, &ct); \ + } \ + lua_pop(L, 1); \ + break; \ + \ + case LUA_TNIL: \ + real = (TYPE)0; \ + break; \ + \ + default: \ + type_error(L, idx, #TYPE, 0, NULL); \ + } + +static int64_t cast_int64(lua_State* L, int idx, int is_cast) { + TO_NUMBER(int64_t, is_cast); + (void)imag; + return real; +} + +static uint64_t cast_uint64(lua_State* L, int idx, int is_cast) { + TO_NUMBER(uint64_t, is_cast); + (void)imag; + return real; +} + +int32_t check_int32(lua_State* L, int idx) { return (int32_t)cast_int64(L, idx, 0); } + +uint32_t check_uint32(lua_State* L, int idx) { return (uint32_t)cast_uint64(L, idx, 0); } + +int64_t check_int64(lua_State* L, int idx) { return cast_int64(L, idx, 0); } + +uint64_t check_uint64(lua_State* L, int idx) { return cast_uint64(L, idx, 0); } + +static void do_check_double(lua_State* L, int idx, double* preal, double* pimag) { + TO_NUMBER(double, 0); + if (preal) *preal = real; + if (pimag) *pimag = imag; +} + +double check_double(lua_State* L, int idx) { + double ret; + do_check_double(L, idx, &ret, NULL); + return ret; +} + +float check_float(lua_State* L, int idx) { + double ret; + do_check_double(L, idx, &ret, NULL); + return ret; +} + +uintptr_t check_uintptr(lua_State* L, int idx) { + TO_NUMBER(uintptr_t, 1); + (void)imag; + return real; +} + +#ifdef HAVE_COMPLEX +complex_double check_complex_double(lua_State* L, int idx) { + double real, imag; + do_check_double(L, idx, &real, &imag); + return real + imag * 1i; +} + +complex_float check_complex_float(lua_State* L, int idx) { + double real, imag; + do_check_double(L, idx, &real, &imag); + return real + imag * 1i; +} + +#else +complex_double check_complex_double(lua_State* L, int idx) { + complex_double c; + do_check_double(L, idx, &c.real, &c.imag); + return c; +} + +complex_float check_complex_float(lua_State* L, int idx) { + complex_double d; + complex_float f; + do_check_double(L, idx, &d.real, &d.imag); + f.real = d.real; + f.imag = d.imag; + return f; +} +#endif + +static size_t unpack_vararg(lua_State* L, int i, char* to) { + void* p; + struct ctype ct; + + switch (lua_type(L, i)) { + case LUA_TBOOLEAN: + *(int*)to = lua_toboolean(L, i); + return sizeof(int); + + case LUA_TNUMBER: + *(double*)to = lua_tonumber(L, i); + return sizeof(double); + + case LUA_TSTRING: + *(const char**)to = lua_tostring(L, i); + return sizeof(const char*); + + case LUA_TLIGHTUSERDATA: + *(void**)to = lua_touserdata(L, i); + return sizeof(void*); + + case LUA_TUSERDATA: + p = to_cdata(L, i, &ct); + + if (ct.type == INVALID_TYPE) { + *(void**)to = p; + return sizeof(void*); + } + + lua_pop(L, 1); + + if (ct.pointers || ct.type == INTPTR_TYPE) { + *(void**)to = p; + return sizeof(void*); + + } else if (ct.type == INT32_TYPE) { + *(int32_t*)to = *(int32_t*)p; + return sizeof(int32_t); + + } else if (ct.type == INT64_TYPE) { + *(int64_t*)to = *(int64_t*)p; + return sizeof(int64_t); + } + goto err; + + case LUA_TNIL: + *(void**)to = NULL; + return sizeof(void*); + + default: + goto err; + } + +err: + return type_error(L, i, "vararg", 0, NULL); +} + +void unpack_varargs_stack(lua_State* L, int first, int last, char* to) { + int i; + + for (i = first; i <= last; i++) { + to += unpack_vararg(L, i, to); + } +} + +void unpack_varargs_stack_skip(lua_State* L, int first, int last, int ints_to_skip, + int floats_to_skip, char* to) { + int i; + + for (i = first; i <= last; i++) { + int type = lua_type(L, i); + + if (type == LUA_TNUMBER && --floats_to_skip >= 0) { + continue; + } else if (type != LUA_TNUMBER && --ints_to_skip >= 0) { + continue; + } + + to += unpack_vararg(L, i, to); + } +} + +void unpack_varargs_float(lua_State* L, int first, int last, int max, char* to) { + int i; + + for (i = first; i <= last && max > 0; i++) { + if (lua_type(L, i) == LUA_TNUMBER) { + unpack_vararg(L, i, to); + to += sizeof(double); + max--; + } + } +} + +void unpack_varargs_int(lua_State* L, int first, int last, int max, char* to) { + int i; + + for (i = first; i <= last && max > 0; i++) { + if (lua_type(L, i) != LUA_TNUMBER) { + unpack_vararg(L, i, to); + to += sizeof(void*); + max--; + } + } +} + +void unpack_varargs_reg(lua_State* L, int first, int last, char* to) { + int i; + + for (i = first; i <= last; i++) { + unpack_vararg(L, i, to); + to += sizeof(double); + } +} + +/* to_enum tries to convert a value at idx to the enum type indicated by to_ct + * and uv to_usr. For strings this means it will do a string lookup for the + * enum type. It leaves the stack unchanged. Will throw an error if the type + * at idx can't be conerted. + */ +int32_t check_enum(lua_State* L, int idx, int to_usr, const struct ctype* to_ct) { + int32_t ret; + + switch (lua_type(L, idx)) { + case LUA_TSTRING: + /* lookup string in to_usr to find value */ + to_usr = lua_absindex(L, to_usr); + lua_pushvalue(L, idx); + lua_rawget(L, to_usr); + + if (lua_isnil(L, -1)) { + goto err; + } + + ret = (int32_t)lua_tointeger(L, -1); + lua_pop(L, 1); + return ret; + + case LUA_TUSERDATA: + return check_int32(L, idx); + + case LUA_TNIL: + return (int32_t)0; + + case LUA_TNUMBER: + return (int32_t)lua_tointeger(L, idx); + + default: + goto err; + } + +err: + return type_error(L, idx, NULL, to_usr, to_ct); +} + +/* to_pointer tries converts a value at idx to a pointer. It fills out ct and + * pushes the uv of the found type. It will throw a lua error if it can not + * convert the value to a pointer. */ +static void* check_pointer(lua_State* L, int idx, struct ctype* ct) { + void* p; + memset(ct, 0, sizeof(*ct)); + ct->pointers = 1; + idx = lua_absindex(L, idx); + + switch (lua_type(L, idx)) { + case LUA_TNIL: + ct->type = VOID_TYPE; + ct->is_null = 1; + lua_pushnil(L); + return NULL; + + case LUA_TNUMBER: + ct->type = INTPTR_TYPE; + ct->is_unsigned = 1; + ct->pointers = 0; + lua_pushnil(L); + return (void*)(uintptr_t)lua_tonumber(L, idx); + + case LUA_TLIGHTUSERDATA: + ct->type = VOID_TYPE; + lua_pushnil(L); + return lua_touserdata(L, idx); + + case LUA_TSTRING: + ct->type = INT8_TYPE; + ct->is_unsigned = IS_CHAR_UNSIGNED; + ct->is_array = 1; + ct->base_size = 1; + ct->const_mask = 2; + lua_pushnil(L); + return (void*)lua_tolstring(L, idx, &ct->array_size); + + case LUA_TUSERDATA: + p = to_cdata(L, idx, ct); + + if (ct->type == INVALID_TYPE) { + /* some other type of user data */ + ct->type = VOID_TYPE; + return lua_touserdata(L, idx); + } else if (ct->type == STRUCT_TYPE || ct->type == UNION_TYPE) { + return p; + } else { + return (void*)(intptr_t)check_intptr(L, idx, p, ct); + } + break; + } + + type_error(L, idx, "pointer", 0, NULL); + return NULL; +} + +static int is_void_ptr(const struct ctype* ct) { + return ct->type == VOID_TYPE && ct->pointers == 1; +} + +static int is_same_type(lua_State* L, int usr1, int usr2, const struct ctype* t1, + const struct ctype* t2) { + if (t1->type != t2->type) { + return 0; + } + +#if LUA_VERSION_NUM == 501 + if (lua_isnil(L, usr1) != lua_isnil(L, usr2)) { + int ret; + usr1 = lua_absindex(L, usr1); + usr2 = lua_absindex(L, usr2); + push_upval(L, &niluv_key); + + ret = lua_rawequal(L, usr1, -1) || lua_rawequal(L, usr2, -1); + + lua_pop(L, 1); + + if (ret) { + return 1; + } + } +#endif + + return lua_rawequal(L, usr1, usr2); +} + +static void set_struct(lua_State* L, int idx, void* to, int to_usr, + const struct ctype* tt, int check_pointers); + +/* to_typed_pointer converts a value at idx to a type tt with target uv to_usr + * checking all types. May push a temporary value so that it can create + * structs on the fly. */ +void* check_typed_pointer(lua_State* L, int idx, int to_usr, const struct ctype* tt) { + struct ctype ft; + void* p; + + to_usr = lua_absindex(L, to_usr); + idx = lua_absindex(L, idx); + + if (tt->pointers == 1 && (tt->type == STRUCT_TYPE || tt->type == UNION_TYPE) && + lua_type(L, idx) == LUA_TTABLE) { + /* need to construct a struct of the target type */ + struct ctype ct = *tt; + ct.pointers = ct.is_array = 0; + p = push_cdata(L, to_usr, &ct); + set_struct(L, idx, p, to_usr, &ct, 1); + return p; + } + + p = check_pointer(L, idx, &ft); + + if (tt->pointers == 1 && ft.pointers == 0 && + (ft.type == STRUCT_TYPE || ft.type == UNION_TYPE)) { + /* auto dereference structs */ + ft.pointers = 1; + ft.const_mask <<= 1; + } + + if (is_void_ptr(tt)) { + /* any pointer can convert to void* */ + goto suc; + + } else if (ft.is_null) { + /* NULL can convert to any pointer */ + goto suc; + + } else if (!is_same_type(L, to_usr, -1, tt, &ft)) { + /* the base type is different */ + goto err; + + } else if (tt->pointers != ft.pointers) { + goto err; + + } else if (ft.const_mask & ~tt->const_mask) { + /* for every const in from it must be in to, there are further rules + * for const casting (see the c++ spec), but they are hard to test + * quickly */ + goto err; + } + +suc: + return p; + +err: + type_error(L, idx, NULL, to_usr, tt); + return NULL; +} + +/* to_cfunction converts a value at idx with usr table at to_usr and type tt + * into a function. Leaves the stack unchanged. */ +static cfunction check_cfunction(lua_State* L, int idx, int to_usr, + const struct ctype* tt, int check_pointers) { + void* p; + struct ctype ft; + int top = lua_gettop(L); + + idx = lua_absindex(L, idx); + to_usr = lua_absindex(L, to_usr); + + switch (lua_type(L, idx)) { + case LUA_TFUNCTION: + assert(!"callbacks are unimplemented"); + return NULL; + + case LUA_TNIL: + return NULL; + + case LUA_TLIGHTUSERDATA: + if (check_pointers) { + goto err; + } else { + return (cfunction)lua_touserdata(L, idx); + } + + case LUA_TUSERDATA: + p = to_cdata(L, idx, &ft); + assert(lua_gettop(L) == top + 1); + + if (ft.type == INVALID_TYPE) { + if (check_pointers) { + goto err; + } else { + lua_pop(L, 1); + return (cfunction)lua_touserdata(L, idx); + } + + } else if (ft.is_null) { + lua_pop(L, 1); + return NULL; + + } else if (!check_pointers && (ft.pointers || ft.type == INTPTR_TYPE)) { + lua_pop(L, 1); + return (cfunction) * (void**)p; + + } else if (ft.type != FUNCTION_PTR_TYPE) { + goto err; + + } else if (!check_pointers) { + lua_pop(L, 1); + return *(cfunction*)p; + + } else if (ft.calling_convention != tt->calling_convention) { + goto err; + + } else if (!is_same_type(L, -1, to_usr, &ft, tt)) { + goto err; + + } else { + lua_pop(L, 1); + return *(cfunction*)p; + } + + default: + goto err; + } + +err: + type_error(L, idx, NULL, to_usr, tt); + return NULL; +} + +/* to_type_cfunction converts a value at idx with uv at to_usr and type tt to + * a cfunction. Leaves the stack unchanged. */ +cfunction check_typed_cfunction(lua_State* L, int idx, int to_usr, + const struct ctype* tt) { + return check_cfunction(L, idx, to_usr, tt, 1); +} + +static void set_value(lua_State* L, int idx, void* to, int to_usr, const struct ctype* tt, + int check_pointers); + +static void set_array(lua_State* L, int idx, void* to, int to_usr, const struct ctype* tt, + int check_pointers) { + size_t i, sz, esz; + struct ctype et; + + idx = lua_absindex(L, idx); + to_usr = lua_absindex(L, to_usr); + + switch (lua_type(L, idx)) { + case LUA_TSTRING: + if (tt->pointers == 1 && tt->type == INT8_TYPE) { + const char* str = lua_tolstring(L, idx, &sz); + + if (!tt->is_variable_array && sz >= tt->array_size) { + memcpy(to, str, tt->array_size); + } else { + /* include nul terminator */ + memcpy(to, str, sz + 1); + } + } else { + goto err; + } + break; + + case LUA_TTABLE: + et = *tt; + et.pointers--; + et.const_mask >>= 1; + et.is_array = 0; + esz = et.pointers ? sizeof(void*) : et.base_size; + + lua_rawgeti(L, idx, 2); + + if (tt->is_variable_array) { + /* we have no idea how big the array is, so set values based off + * how many items were given to us */ + lua_pop(L, 1); + for (i = 0; i < lua_rawlen(L, idx); i++) { + lua_rawgeti(L, idx, (int)i + 1); + set_value(L, -1, (char*)to + esz * i, to_usr, &et, check_pointers); + lua_pop(L, 1); + } + + } else if (lua_isnil(L, -1)) { + /* there is no second element, so we set the whole array to the + * first element (or nil - ie 0) if there is no first element) */ + lua_pop(L, 1); + lua_rawgeti(L, idx, 1); + + if (lua_isnil(L, -1)) { + memset(to, 0, ctype_size(L, tt)); + } else { + /* if its still variable we have no idea how many values to set */ + for (i = 0; i < tt->array_size; i++) { + set_value(L, -1, (char*)to + esz * i, to_usr, &et, + check_pointers); + } + } + + lua_pop(L, 1); + + } else { + /* there is a second element, so we set each element using the + * equiv index in the table initializer */ + lua_pop(L, 1); + for (i = 0; i < tt->array_size; i++) { + lua_rawgeti(L, idx, (int)(i + 1)); + + if (lua_isnil(L, -1)) { + /* we've hit the end of the values provided in the + * initializer, so memset the rest to zero */ + lua_pop(L, 1); + memset((char*)to + esz * i, 0, (tt->array_size - i) * esz); + break; + + } else { + set_value(L, -1, (char*)to + esz * i, to_usr, &et, + check_pointers); + lua_pop(L, 1); + } + } + } + break; + + default: + goto err; + } + + return; + +err: + type_error(L, idx, NULL, to_usr, tt); +} + +/* pops the member key from the stack, leaves the member user value on the + * stack. Returns the member offset. Returns -ve if the member can not be + * found. */ +static ptrdiff_t get_member(lua_State* L, int usr, const struct ctype* ct, + struct ctype* mt) { + ptrdiff_t off; + lua_rawget(L, usr); + + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + return -1; + } + + *mt = *(const struct ctype*)lua_touserdata(L, -1); + lua_getuservalue(L, -1); + lua_replace(L, -2); + + if (mt->is_variable_array && ct->variable_size_known) { + /* eg char mbr[?] */ + size_t sz = (mt->pointers > 1) ? sizeof(void*) : mt->base_size; + assert(ct->is_variable_struct && mt->is_array); + mt->array_size = ct->variable_increment / sz; + mt->is_variable_array = 0; + + } else if (mt->is_variable_struct && ct->variable_size_known) { + /* eg struct {char a; char b[?]} mbr; */ + assert(ct->is_variable_struct); + mt->variable_size_known = 1; + mt->variable_increment = ct->variable_increment; + } + + off = mt->offset; + mt->offset = 0; + return off; +} + +static void set_struct(lua_State* L, int idx, void* to, int to_usr, + const struct ctype* tt, int check_pointers) { + int have_first = 0; + int have_other = 0; + struct ctype mt; + void* p; + + to_usr = lua_absindex(L, to_usr); + idx = lua_absindex(L, idx); + + switch (lua_type(L, idx)) { + case LUA_TTABLE: + /* match up to the members based off the table initializers key - this + * will match both numbered and named members in the user table + * we need a special case for when no entries in the initializer - + * zero initialize the c struct, and only one entry in the initializer + * - set all members to this value */ + memset(to, 0, ctype_size(L, tt)); + lua_pushnil(L); + while (lua_next(L, idx)) { + ptrdiff_t off; + + if (!have_first && lua_tonumber(L, -2) == 1 && lua_tonumber(L, -1) != 0) { + have_first = 1; + } else if (!have_other && + (lua_type(L, -2) != LUA_TNUMBER || lua_tonumber(L, -2) != 1)) { + have_other = 1; + } + + lua_pushvalue(L, -2); + off = get_member(L, to_usr, tt, &mt); + assert(off >= 0); + set_value(L, -2, (char*)to + off, -1, &mt, check_pointers); + + /* initializer value, mt usr */ + lua_pop(L, 2); + } + + /* if we only had a single non zero value then initialize all members to that + * value */ + if (!have_other && have_first && tt->type != UNION_TYPE) { + size_t i, sz; + ptrdiff_t off; + lua_rawgeti(L, idx, 1); + sz = lua_rawlen(L, to_usr); + + for (i = 2; i < sz; i++) { + lua_pushnumber(L, i); + off = get_member(L, to_usr, tt, &mt); + assert(off >= 0); + set_value(L, -2, (char*)to + off, -1, &mt, check_pointers); + lua_pop(L, 1); /* mt usr */ + } + + lua_pop(L, 1); /* initializer table */ + } + break; + + case LUA_TUSERDATA: + if (check_pointers) { + p = check_typed_pointer(L, idx, to_usr, tt); + } else { + struct ctype ct; + p = check_pointer(L, idx, &ct); + } + memcpy(to, p, tt->base_size); + lua_pop(L, 1); + break; + + default: + goto err; + } + + return; + +err: + type_error(L, idx, NULL, to_usr, tt); +} + +static void set_value(lua_State* L, int idx, void* to, int to_usr, const struct ctype* tt, + int check_pointers) { + int top = lua_gettop(L); + + if (tt->is_array) { + set_array(L, idx, to, to_usr, tt, check_pointers); + + } else if (tt->pointers) { + union { + uint8_t c[sizeof(void*)]; + void* p; + } u; + + if (lua_istable(L, idx)) { + luaL_error(L, + "Can't set a pointer member to a struct that's about to be freed"); + } + + if (check_pointers) { + u.p = check_typed_pointer(L, idx, to_usr, tt); + } else { + struct ctype ct; + u.p = check_pointer(L, idx, &ct); + } + +#ifndef ALLOW_MISALIGNED_ACCESS + if ((uintptr_t)to & PTR_ALIGN_MASK) { + memcpy(to, u.c, sizeof(void*)); + } else +#endif + { + *(void**)to = u.p; + } + + lua_pop(L, 1); + + } else if (tt->is_bitfield) { + uint64_t hi_mask = UINT64_C(0) - (UINT64_C(1) << (tt->bit_offset + tt->bit_size)); + uint64_t low_mask = (UINT64_C(1) << tt->bit_offset) - UINT64_C(1); + uint64_t val = check_uint64(L, idx); + val &= (UINT64_C(1) << tt->bit_size) - 1; + val <<= tt->bit_offset; + *(uint64_t*)to = val | (*(uint64_t*)to & (hi_mask | low_mask)); + + } else if (tt->type == STRUCT_TYPE || tt->type == UNION_TYPE) { + set_struct(L, idx, to, to_usr, tt, check_pointers); + + } else { +#ifndef ALLOW_MISALIGNED_ACCESS + union { + uint8_t c[8]; + _Bool b; + uint64_t u64; + float f; + double d; + cfunction func; + } misalign; + + void* origto = to; + + if ((uintptr_t)origto & (tt->base_size - 1)) { + to = misalign.c; + } +#endif + + switch (tt->type) { + case BOOL_TYPE: + *(_Bool*)to = (cast_int64(L, idx, !check_pointers) != 0); + break; + case INT8_TYPE: + if (tt->is_unsigned) { + *(uint8_t*)to = (uint8_t)cast_uint64(L, idx, !check_pointers); + } else { + *(int8_t*)to = (int8_t)cast_int64(L, idx, !check_pointers); + } + break; + case INT16_TYPE: + if (tt->is_unsigned) { + *(uint16_t*)to = (uint16_t)cast_uint64(L, idx, !check_pointers); + } else { + *(int16_t*)to = (int16_t)cast_int64(L, idx, !check_pointers); + } + break; + case INT32_TYPE: + if (tt->is_unsigned) { + *(uint32_t*)to = (uint32_t)cast_uint64(L, idx, !check_pointers); + } else { + *(int32_t*)to = (int32_t)cast_int64(L, idx, !check_pointers); + } + break; + case INT64_TYPE: + if (tt->is_unsigned) { + *(uint64_t*)to = cast_uint64(L, idx, !check_pointers); + } else { + *(int64_t*)to = cast_int64(L, idx, !check_pointers); + } + break; + case FLOAT_TYPE: + *(float*)to = (float)check_double(L, idx); + break; + case DOUBLE_TYPE: + *(double*)to = check_double(L, idx); + break; + case COMPLEX_FLOAT_TYPE: + *(complex_float*)to = check_complex_float(L, idx); + break; + case COMPLEX_DOUBLE_TYPE: + *(complex_double*)to = check_complex_double(L, idx); + break; + case INTPTR_TYPE: + *(uintptr_t*)to = check_uintptr(L, idx); + break; + case ENUM_TYPE: + *(int32_t*)to = check_enum(L, idx, to_usr, tt); + break; + case FUNCTION_PTR_TYPE: + *(cfunction*)to = check_cfunction(L, idx, to_usr, tt, check_pointers); + break; + default: + goto err; + } + +#ifndef ALLOW_MISALIGNED_ACCESS + if ((uintptr_t)origto & (tt->base_size - 1)) { + memcpy(origto, misalign.c, tt->base_size); + } +#endif + } + + assert(lua_gettop(L) == top); + return; +err: + type_error(L, idx, NULL, to_usr, tt); +} + +static int ffi_typeof(lua_State* L) { + struct ctype ct; + check_ctype(L, 1, &ct); + push_ctype(L, -1, &ct); + return 1; +} + +static void setmintop(lua_State* L, int idx) { + if (lua_gettop(L) < idx) { + lua_settop(L, idx); + } +} + +/* warning: in the case that it finds an array size, it removes that index */ +static void get_variable_array_size(lua_State* L, int idx, struct ctype* ct) { + /* we only care about the variable buisness for the variable array + * directly ie ffi.new('char[?]') or the struct that contains the variable + * array ffi.new('struct {char v[?]}'). A pointer to the struct doesn't + * care about the variable size (it treats it as a zero sized array). */ + + if (ct->is_variable_array) { + assert(ct->is_array); + ct->array_size = (size_t)luaL_checknumber(L, idx); + ct->is_variable_array = 0; + lua_remove(L, idx); + + } else if (ct->is_variable_struct && !ct->variable_size_known) { + assert(ct->type == STRUCT_TYPE && !ct->is_array); + ct->variable_increment *= (size_t)luaL_checknumber(L, idx); + ct->variable_size_known = 1; + lua_remove(L, idx); + } +} + +static int try_set_value(lua_State* L) { + void* p = lua_touserdata(L, 2); + struct ctype* ct = (struct ctype*)lua_touserdata(L, 4); + int check_ptrs = lua_toboolean(L, 5); + set_value(L, 1, p, 3, ct, check_ptrs); + return 0; +} + +static int do_new(lua_State* L, int is_cast) { + int cargs, i; + void* p; + struct ctype ct; + int check_ptrs = !is_cast; + + check_ctype(L, 1, &ct); + + /* don't push a callback when we have a c function, as cb:set needs a + * compiled callback from a lua function to work */ + if (!ct.pointers && ct.type == FUNCTION_PTR_TYPE && + (lua_isnil(L, 2) || lua_isfunction(L, 2))) { + assert(!"callbacks are not implemented"); + return 1; + } + + /* this removes the vararg argument if its needed, and errors if its invalid */ + if (!is_cast) { + get_variable_array_size(L, 2, &ct); + } + + p = push_cdata(L, -1, &ct); + + /* if the user mt has a __gc function then call ffi.gc on this value */ + if (push_user_mt(L, -2, &ct)) { + push_upval(L, &gc_key); + lua_pushvalue(L, -3); + + /* user_mt.__gc */ + lua_pushliteral(L, "__gc"); + lua_rawget(L, -4); + + lua_rawset(L, -3); /* gc_upval[cdata] = user_mt.__gc */ + lua_pop(L, 2); /* user_mt and gc_upval */ + } + + /* stack is: + * ctype arg + * ctor args ... 0+ + * ctype usr + * cdata + */ + + cargs = lua_gettop(L) - 3; + + if (cargs == 0) { + return 1; + } + + if (cargs == 1) { + /* try packed form first + * packed: ffi.new('int[3]', {1}) + * unpacked: ffi.new('int[3]', 1) + */ + lua_pushcfunction(L, &try_set_value); + lua_pushvalue(L, 2); /* ctor arg */ + lua_pushlightuserdata(L, p); + lua_pushvalue(L, -5); /* ctype usr */ + lua_pushlightuserdata(L, &ct); + lua_pushboolean(L, check_ptrs); + + if (!lua_pcall(L, 5, 0, 0)) { + return 1; + } + + /* remove any errors */ + lua_settop(L, 4); + } + + /* if we have more than 2 ctor arguments then they must be unpacked, e.g. + * ffi.new('int[3]', 1, 2, 3) */ + lua_createtable(L, cargs, 0); + lua_replace(L, 1); + for (i = 1; i <= cargs; i++) { + lua_pushvalue(L, i + 1); + lua_rawseti(L, 1, i); + } + assert(lua_gettop(L) == cargs + 3); + set_value(L, 1, p, -2, &ct, check_ptrs); + + return 1; +} + +static int ffi_new(lua_State* L) { return do_new(L, 0); } + +static int ffi_cast(lua_State* L) { return do_new(L, 1); } + +static int ctype_new(lua_State* L) { return do_new(L, 0); } + +static int ctype_call(lua_State* L) { + struct ctype ct; + int top = lua_gettop(L); + + check_ctype(L, 1, &ct); + + if (push_user_mt(L, -1, &ct)) { + lua_pushstring(L, "__new"); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) { + lua_insert(L, 1); // function at bottom of stack under args + lua_pop(L, 2); + lua_call(L, top, 1); + return 1; + } + lua_pop(L, 2); + } + lua_pop(L, 1); + + assert(lua_gettop(L) == top); + return do_new(L, 0); +} + +static int ffi_sizeof(lua_State* L) { + struct ctype ct; + check_ctype(L, 1, &ct); + get_variable_array_size(L, 2, &ct); + lua_pushnumber(L, ctype_size(L, &ct)); + return 1; +} + +static int ffi_alignof(lua_State* L) { + struct ctype ct, mt; + lua_settop(L, 2); + check_ctype(L, 1, &ct); + + /* if no member is specified then we return the alignment of the type */ + if (lua_isnil(L, 2)) { + lua_pushnumber(L, ct.align_mask + 1); + return 1; + } + + /* get the alignment of the member */ + lua_pushvalue(L, 2); + if (get_member(L, -2, &ct, &mt) < 0) { + push_type_name(L, 3, &ct); + return luaL_error(L, "type %s has no member %s", lua_tostring(L, -1), + lua_tostring(L, 2)); + } + + lua_pushnumber(L, mt.align_mask + 1); + return 1; +} + +static int ffi_offsetof(lua_State* L) { + ptrdiff_t off; + struct ctype ct, mt; + lua_settop(L, 2); + check_ctype(L, 1, &ct); + + lua_pushvalue(L, 2); + off = get_member(L, -2, &ct, + &mt); /* this replaces the member key at -1 with the mbr usr value */ + if (off < 0) { + push_type_name(L, 3, &ct); + return luaL_error(L, "type %s has no member %s", lua_tostring(L, -1), + lua_tostring(L, 2)); + } + + lua_pushnumber(L, off); + + if (!mt.is_bitfield) { + return 1; + } + + lua_pushnumber(L, mt.bit_offset); + lua_pushnumber(L, mt.bit_size); + return 3; +} + +static int ffi_istype(lua_State* L) { + struct ctype tt, ft; + check_ctype(L, 1, &tt); + to_cdata(L, 2, &ft); + + if (ft.type == INVALID_TYPE) { + goto fail; + } + + if (!is_same_type(L, 3, 4, &tt, &ft)) { + goto fail; + } + + if (tt.pointers != ft.pointers) { + goto fail; + } + + if (tt.is_array != ft.is_array) { + goto fail; + } + + if (tt.is_array && tt.array_size != ft.array_size) { + goto fail; + } + + if (tt.calling_convention != ft.calling_convention) { + goto fail; + } + + lua_pushboolean(L, 1); + return 1; + +fail: + lua_pushboolean(L, 0); + return 1; +} + +static int cdata_gc(lua_State* L) { + struct ctype ct; + check_cdata(L, 1, &ct); + lua_settop(L, 1); + + /* call the gc func if there is any registered */ + lua_pushvalue(L, 1); + lua_rawget(L, lua_upvalueindex(1)); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, 1); + lua_pcall(L, 1, 0, 0); + } + + /* unset the closure */ + lua_pushvalue(L, 1); + lua_pushnil(L); + lua_rawset(L, lua_upvalueindex(1)); + + return 0; +} + +static int user_mt_key; + +static int ffi_metatype(lua_State* L) { + struct ctype ct; + lua_settop(L, 2); + + check_ctype(L, 1, &ct); + if (lua_type(L, 2) != LUA_TTABLE && lua_type(L, 2) != LUA_TNIL) { + return luaL_argerror(L, 2, "metatable must be a table or nil"); + } + + lua_pushlightuserdata(L, &user_mt_key); + lua_pushvalue(L, 2); + lua_rawset(L, 3); /* user[user_mt_key] = mt */ + + /* return the passed in ctype */ + push_ctype(L, 3, &ct); + return 1; +} + +/* push_user_mt returns 1 if the type has a user metatable and pushes it onto + * the stack, otherwise it returns 0 and pushes nothing */ +int push_user_mt(lua_State* L, int ct_usr, const struct ctype* ct) { + if (ct->type != STRUCT_TYPE && ct->type != UNION_TYPE) { + return 0; + } + + ct_usr = lua_absindex(L, ct_usr); + lua_pushlightuserdata(L, &user_mt_key); + lua_rawget(L, ct_usr); + + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + return 0; + } + return 1; +} + +static int ffi_gc(lua_State* L) { + struct ctype ct; + lua_settop(L, 2); + check_cdata(L, 1, &ct); + + push_upval(L, &gc_key); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_rawset(L, -3); + + /* return the cdata back */ + lua_settop(L, 1); + return 1; +} + +/* lookup_cdata_index returns the offset of the found type and user value on + * the stack if valid. Otherwise returns -ve and doesn't touch the stack. + */ +static ptrdiff_t lookup_cdata_index(lua_State* L, int idx, int ct_usr, struct ctype* ct) { + struct ctype mt; + ptrdiff_t off; + + ct_usr = lua_absindex(L, ct_usr); + + switch (lua_type(L, idx)) { + case LUA_TNUMBER: + /* possibilities are array, pointer */ + + if (!ct->pointers || is_void_ptr(ct)) { + return -1; + } + + ct->is_array = 0; + ct->pointers--; + ct->const_mask >>= 1; + + lua_pushvalue(L, ct_usr); + + return (ct->pointers ? sizeof(void*) : ct->base_size) * lua_tonumber(L, 2); + + case LUA_TSTRING: + /* possibilities are struct/union, pointer to struct/union */ + + if ((ct->type != STRUCT_TYPE && ct->type != UNION_TYPE) || ct->is_array || + ct->pointers > 1) { + return -1; + } + + lua_pushvalue(L, idx); + off = get_member(L, ct_usr, ct, &mt); + if (off < 0) { + return -1; + } + + *ct = mt; + return off; + + default: + return -1; + } +} + +static int cdata_newindex(lua_State* L) { + struct ctype tt; + char* to; + ptrdiff_t off; + + lua_settop(L, 3); + + to = (char*)check_cdata(L, 1, &tt); + off = lookup_cdata_index(L, 2, -1, &tt); + + if (off < 0) { + if (!push_user_mt(L, -1, &tt)) { + goto err; + } + + lua_pushliteral(L, "__newindex"); + lua_rawget(L, -2); + + if (lua_isnil(L, -1)) { + goto err; + } + + lua_insert(L, 1); + lua_settop(L, 4); + lua_call(L, 3, LUA_MULTRET); + return lua_gettop(L); + } + + if (tt.const_mask & 1) { + return luaL_error(L, "can't set const data"); + } + + set_value(L, 3, to + off, -1, &tt, 1); + return 0; + +err: + push_type_name(L, 4, &tt); + return luaL_error(L, "type %s has no member %s", lua_tostring(L, -1), + lua_tostring(L, 2)); +} + +static int cdata_index(lua_State* L) { + void* to; + struct ctype ct; + char* data; + ptrdiff_t off; + + lua_settop(L, 2); + data = (char*)check_cdata(L, 1, &ct); + assert(lua_gettop(L) == 3); + + if (!ct.pointers) { + switch (ct.type) { + case FUNCTION_PTR_TYPE: + /* Callbacks use the same metatable as standard cdata values, but have set + * and free members. So instead of mt.__index = mt, we do the equiv here. + */ + lua_getmetatable(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + return 1; + + /* This provides the .re and .im virtual members */ + case COMPLEX_DOUBLE_TYPE: + case COMPLEX_FLOAT_TYPE: + if (!lua_isstring(L, 2)) { + luaL_error(L, "invalid member for complex number"); + + } else if (strcmp(lua_tostring(L, 2), "re") == 0) { + lua_pushnumber(L, ct.type == COMPLEX_DOUBLE_TYPE + ? creal(*(complex_double*)data) + : crealf(*(complex_float*)data)); + + } else if (strcmp(lua_tostring(L, 2), "im") == 0) { + lua_pushnumber(L, ct.type == COMPLEX_DOUBLE_TYPE + ? cimag(*(complex_double*)data) + : cimagf(*(complex_float*)data)); + + } else { + luaL_error(L, "invalid member for complex number"); + } + return 1; + } + } + + off = lookup_cdata_index(L, 2, -1, &ct); + + if (off < 0) { + assert(lua_gettop(L) == 3); + if (!push_user_mt(L, -1, &ct)) { + goto err; + } + + lua_pushliteral(L, "__index"); + lua_rawget(L, -2); + + if (lua_isnil(L, -1)) { + goto err; + } + + if (lua_istable(L, -1)) { + lua_pushvalue(L, 2); + lua_gettable(L, -2); + return 1; + } + + lua_insert(L, 1); + lua_settop(L, 3); + lua_call(L, 2, LUA_MULTRET); + return lua_gettop(L); + + err: + push_type_name(L, 3, &ct); + return luaL_error(L, "type %s has no member %s", lua_tostring(L, -1), + lua_tostring(L, 2)); + } + + assert(lua_gettop(L) == 4); /* ct, key, ct_usr, mbr_usr */ + data += off; + + if (ct.is_array) { + /* push a reference to the array */ + ct.is_reference = 1; + to = push_cdata(L, -1, &ct); + *(void**)to = data; + return 1; + + } else if (ct.is_bitfield) { + if (ct.type == INT64_TYPE) { + struct ctype rt; + uint64_t val = *(uint64_t*)data; + val >>= ct.bit_offset; + val &= (UINT64_C(1) << ct.bit_size) - 1; + + memset(&rt, 0, sizeof(rt)); + rt.base_size = 8; + rt.type = INT64_TYPE; + rt.is_unsigned = 1; + rt.is_defined = 1; + + to = push_cdata(L, 0, &rt); + *(uint64_t*)to = val; + + return 1; + + } else if (ct.type == BOOL_TYPE) { + uint64_t val = *(uint64_t*)data; + lua_pushboolean(L, (int)(val & (UINT64_C(1) << ct.bit_offset))); + return 1; + + } else { + uint64_t val = *(uint64_t*)data; + val >>= ct.bit_offset; + val &= (UINT64_C(1) << ct.bit_size) - 1; + lua_pushnumber(L, val); + return 1; + } + + } else if (ct.pointers) { +#ifndef ALLOW_MISALIGNED_ACCESS + union { + uint8_t c[8]; + void* p; + } misalignbuf; + + if ((uintptr_t)data & PTR_ALIGN_MASK) { + memcpy(misalignbuf.c, data, sizeof(void*)); + data = misalignbuf.c; + } +#endif + to = push_cdata(L, -1, &ct); + *(void**)to = *(void**)data; + return 1; + + } else if (ct.type == STRUCT_TYPE || ct.type == UNION_TYPE) { + /* push a reference to the member */ + ct.is_reference = 1; + to = push_cdata(L, -1, &ct); + *(void**)to = data; + return 1; + + } else if (ct.type == FUNCTION_PTR_TYPE) { + cfunction* pf = (cfunction*)push_cdata(L, -1, &ct); + *pf = *(cfunction*)data; + return 1; + + } else { +#ifndef ALLOW_MISALIGNED_ACCESS + union { + uint8_t c[8]; + double d; + float f; + uint64_t u64; + } misalignbuf; + + assert(ct.base_size <= 8); + + if ((uintptr_t)data & (ct.base_size - 1)) { + memcpy(misalignbuf.c, data, ct.base_size); + data = misalignbuf.c; + } +#endif + + switch (ct.type) { + case BOOL_TYPE: + lua_pushboolean(L, *(_Bool*)data); + break; + case INT8_TYPE: + lua_pushnumber(L, ct.is_unsigned ? (lua_Number) * (uint8_t*)data + : (lua_Number) * (int8_t*)data); + break; + case INT16_TYPE: + lua_pushnumber(L, ct.is_unsigned ? (lua_Number) * (uint16_t*)data + : (lua_Number) * (int16_t*)data); + break; + case ENUM_TYPE: + case INT32_TYPE: + lua_pushnumber(L, ct.is_unsigned ? (lua_Number) * (uint32_t*)data + : (lua_Number) * (int32_t*)data); + break; + case INT64_TYPE: + to = push_cdata(L, -1, &ct); + *(int64_t*)to = *(int64_t*)data; + break; + case INTPTR_TYPE: + to = push_cdata(L, -1, &ct); + *(intptr_t*)to = *(intptr_t*)data; + break; + case FLOAT_TYPE: + lua_pushnumber(L, *(float*)data); + break; + case DOUBLE_TYPE: + lua_pushnumber(L, *(double*)data); + break; + default: + luaL_error(L, "internal error: invalid member type"); + } + + return 1; + } +} + +static complex_double check_complex(lua_State* L, int idx, void* p, struct ctype* ct) { + if (ct->type == INVALID_TYPE) { + double d = luaL_checknumber(L, idx); +#ifdef HAVE_COMPLEX + return d; +#else + complex_double c; + c.real = d; + c.imag = 0; + return c; +#endif + } else if (ct->type == COMPLEX_DOUBLE_TYPE) { + return *(complex_double*)p; + } else if (ct->type == COMPLEX_FLOAT_TYPE) { + complex_float* f = (complex_float*)p; +#ifdef HAVE_COMPLEX + return *f; +#else + complex_double d; + d.real = f->real; + d.imag = f->imag; + return d; +#endif + } else { + complex_double dummy; + type_error(L, idx, "complex", 0, NULL); + memset(&dummy, 0, sizeof(dummy)); + return dummy; + } +} + +static int rank(const struct ctype* ct) { + if (ct->pointers) { + return 5; + } + + switch (ct->type) { + case COMPLEX_DOUBLE_TYPE: + return 7; + case COMPLEX_FLOAT_TYPE: + return 6; + case INTPTR_TYPE: + return sizeof(intptr_t) >= sizeof(int64_t) ? 4 : 1; + case INT64_TYPE: + return ct->is_unsigned ? 3 : 2; + case INT32_TYPE: + case INT16_TYPE: + case INT8_TYPE: + return 2; + default: + return 0; + } +} + +static void push_complex(lua_State* L, complex_double res, int ct_usr, + const struct ctype* ct) { + if (ct->type == COMPLEX_DOUBLE_TYPE) { + complex_double* p = (complex_double*)push_cdata(L, ct_usr, ct); + *p = res; + } else { + complex_float* p = (complex_float*)push_cdata(L, ct_usr, ct); +#ifdef HAVE_COMPLEX + *p = (complex float)res; +#else + p->real = (float)res.real; + p->imag = (float)res.imag; +#endif + } +} + +static void push_number(lua_State* L, int64_t val, int ct_usr, const struct ctype* ct) { + if ((ct->pointers || ct->type == INTPTR_TYPE) && + sizeof(intptr_t) != sizeof(int64_t)) { + intptr_t* p = (intptr_t*)push_cdata(L, ct_usr, ct); + *p = val; + } else { + int64_t* p = (int64_t*)push_cdata(L, ct_usr, ct); + *p = val; + } +} + +static int call_user_op(lua_State* L, const char* opfield, int idx, int ct_usr, + const struct ctype* ct) { + idx = lua_absindex(L, idx); + + if (push_user_mt(L, ct_usr, ct)) { + lua_pushstring(L, opfield); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) { + int top = lua_gettop(L); + lua_pushvalue(L, idx); + lua_call(L, 1, LUA_MULTRET); + return lua_gettop(L) - top + 1; + } + lua_pop(L, 2); + } + return -1; +} + +static int cdata_unm(lua_State* L) { + struct ctype ct; + void* p; + int64_t val; + int ret; + + lua_settop(L, 1); + p = to_cdata(L, 1, &ct); + + ret = call_user_op(L, "__unm", 1, 2, &ct); + if (ret >= 0) { + return ret; + } + + val = check_intptr(L, 1, p, &ct); + + if (ct.pointers) { + luaL_error(L, "can't negate a pointer value"); + } else { + memset(&ct, 0, sizeof(ct)); + ct.type = INT64_TYPE; + ct.base_size = 8; + ct.is_defined = 1; + push_number(L, -val, 0, &ct); + } + + return 1; +} + +/* returns -ve if no binop was called otherwise returns the number of return + * arguments */ +static int call_user_binop(lua_State* L, const char* opfield, int lidx, int lusr, + const struct ctype* lt, int ridx, int rusr, + const struct ctype* rt) { + lidx = lua_absindex(L, lidx); + ridx = lua_absindex(L, ridx); + + if (push_user_mt(L, lusr, lt)) { + lua_pushstring(L, opfield); + lua_rawget(L, -2); + + if (!lua_isnil(L, -1)) { + int top = lua_gettop(L); + lua_pushvalue(L, lidx); + lua_pushvalue(L, ridx); + lua_call(L, 2, LUA_MULTRET); + return lua_gettop(L) - top + 1; + } + + lua_pop(L, 2); /* user_mt and user_mt.op */ + } + + if (push_user_mt(L, rusr, rt)) { + lua_pushstring(L, opfield); + lua_rawget(L, -2); + + if (!lua_isnil(L, -1)) { + int top = lua_gettop(L); + lua_pushvalue(L, lidx); + lua_pushvalue(L, ridx); + lua_call(L, 2, LUA_MULTRET); + return lua_gettop(L) - top + 1; + } + + lua_pop(L, 2); /* user_mt and user_mt.op */ + } + + return -1; +} + +static int cdata_concat(lua_State* L) { + struct ctype lt, rt; + int ret; + + lua_settop(L, 2); + to_cdata(L, 1, <); + to_cdata(L, 2, &rt); + + ret = call_user_binop(L, "__concat", 1, 3, <, 2, 4, &rt); + if (ret >= 0) { + return ret; + } + + return luaL_error(L, "NYI"); +} + +static int cdata_len(lua_State* L) { + struct ctype ct; + int ret; + + lua_settop(L, 1); + to_cdata(L, 1, &ct); + + ret = call_user_op(L, "__len", 1, 2, &ct); + if (ret >= 0) { + return ret; + } + + push_type_name(L, 2, &ct); + return luaL_error(L, "type %s does not implement the __len metamethod", + lua_tostring(L, -1)); +} + +static int cdata_pairs(lua_State* L) { + struct ctype ct; + int ret; + + lua_settop(L, 1); + to_cdata(L, 1, &ct); + + ret = call_user_op(L, "__pairs", 1, 2, &ct); + if (ret >= 0) { + return ret; + } + + push_type_name(L, 2, &ct); + return luaL_error(L, "type %s does not implement the __pairs metamethod", + lua_tostring(L, -1)); +} + +static int cdata_ipairs(lua_State* L) { + struct ctype ct; + int ret; + + lua_settop(L, 1); + to_cdata(L, 1, &ct); + + ret = call_user_op(L, "__ipairs", 1, 2, &ct); + if (ret >= 0) { + return ret; + } + + push_type_name(L, 2, &ct); + return luaL_error(L, "type %s does not implement the __ipairs metamethod", + lua_tostring(L, -1)); +} + +static int cdata_add(lua_State* L) { + struct ctype lt, rt, ct; + void *lp, *rp; + int ct_usr; + int ret; + + lua_settop(L, 2); + + lp = to_cdata(L, 1, <); + rp = to_cdata(L, 2, &rt); + assert(lua_gettop(L) == 4); + + ret = call_user_binop(L, "__add", 1, 3, <, 2, 4, &rt); + if (ret >= 0) { + return ret; + } + assert(lua_gettop(L) == 4); + + ct_usr = rank(<) > rank(&rt) ? 3 : 4; + ct = rank(<) > rank(&rt) ? lt : rt; + + if (IS_COMPLEX(ct.type)) { + complex_double left, right, res; + + left = check_complex(L, 1, lp, <); + right = check_complex(L, 2, rp, &rt); + assert(lua_gettop(L) == 4); + +#ifdef HAVE_COMPLEX + res = left + right; +#else + res.real = left.real + right.real; + res.imag = left.imag + right.imag; +#endif + + push_complex(L, res, ct_usr, &ct); + return 1; + + } else { + int64_t left = check_intptr(L, 1, lp, <); + int64_t right = check_intptr(L, 2, rp, &rt); + assert(lua_gettop(L) == 4); + + /* note due to 2s complement it doesn't matter if we do the addition as int or + * uint, but the result needs to be uint64_t if either of the sources are */ + + if (lt.pointers && rt.pointers) { + luaL_error(L, "can't add two pointers"); + + } else if (lt.pointers) { + int64_t res = left + (lt.pointers > 1 ? sizeof(void*) : lt.base_size) * right; + lt.is_array = 0; + push_number(L, res, 3, <); + + } else if (rt.pointers) { + int64_t res = right + (rt.pointers > 1 ? sizeof(void*) : rt.base_size) * left; + rt.is_array = 0; + push_number(L, res, 4, &rt); + + } else { + push_number(L, left + right, ct_usr, &ct); + } + + return 1; + } +} + +static int cdata_sub(lua_State* L) { + struct ctype lt, rt, ct; + void *lp, *rp; + int ct_usr; + int ret; + + lua_settop(L, 2); + + lp = to_cdata(L, 1, <); + rp = to_cdata(L, 2, &rt); + + ret = call_user_binop(L, "__sub", 1, 3, <, 2, 4, &rt); + if (ret >= 0) { + return ret; + } + + ct_usr = rank(<) > rank(&rt) ? 3 : 4; + ct = rank(<) > rank(&rt) ? lt : rt; + + if (IS_COMPLEX(ct.type)) { + complex_double left, right, res; + + left = check_complex(L, 1, lp, <); + right = check_complex(L, 2, rp, &rt); + +#ifdef HAVE_COMPLEX + res = left - right; +#else + res.real = left.real - right.real; + res.imag = left.imag - right.imag; +#endif + + push_complex(L, res, ct_usr, &ct); + return 1; + + } else { + int64_t left = check_intptr(L, 1, lp, <); + int64_t right = check_intptr(L, 2, rp, &rt); + + if (rt.pointers) { + luaL_error(L, "NYI: can't subtract a pointer value"); + + } else if (lt.pointers) { + int64_t res = left - (lt.pointers > 1 ? sizeof(void*) : lt.base_size) * right; + lt.is_array = 0; + push_number(L, res, 3, <); + + } else { + int64_t res = left - right; + push_number(L, res, ct_usr, &ct); + } + + return 1; + } +} + +/* TODO fix for unsigned */ +#define NUMBER_ONLY_BINOP(OPSTR, DO_NORMAL, DO_COMPLEX) \ + struct ctype lt, rt, ct; \ + void *lp, *rp; \ + int ct_usr; \ + int ret; \ + \ + lua_settop(L, 2); \ + \ + lp = to_cdata(L, 1, <); \ + rp = to_cdata(L, 2, &rt); \ + \ + ret = call_user_binop(L, OPSTR, 1, 3, <, 2, 4, &rt); \ + if (ret >= 0) { \ + return ret; \ + } \ + \ + ct_usr = rank(<) > rank(&rt) ? 3 : 4; \ + ct = rank(<) > rank(&rt) ? lt : rt; \ + \ + if (IS_COMPLEX(ct.type)) { \ + complex_double res; \ + complex_double left = check_complex(L, 1, lp, <); \ + complex_double right = check_complex(L, 2, rp, &rt); \ + \ + DO_COMPLEX(left, right, res); \ + push_complex(L, res, ct_usr, &ct); \ + \ + } else if (lt.pointers || rt.pointers) { \ + luaL_error(L, "can't operate on a pointer value"); \ + \ + } else { \ + int64_t res; \ + int64_t left = check_intptr(L, 1, lp, <); \ + int64_t right = check_intptr(L, 2, rp, &rt); \ + \ + DO_NORMAL(left, right, res); \ + push_number(L, res, ct_usr, &ct); \ + } \ + \ + return 1 + +#define MUL(l, r, s) s = l * r +#define DIV(l, r, s) s = l / r +#define MOD(l, r, s) s = l % r +#define POW(l, r, s) s = pow(l, r) + +#ifdef HAVE_COMPLEX +#define MULC(l, r, s) s = l * r +#define DIVC(l, r, s) s = l / r +#define MODC(l, r, s) \ + (void)l, (void)r, memset(&s, 0, sizeof(s)), luaL_error(L, "NYI: complex mod") +#define POWC(l, r, s) s = cpow(l, r) +#else +#define MULC(l, r, s) \ + s.real = l.real * r.real - l.imag * r.imag, s.imag = l.real * r.imag + l.imag * r.real +#define DIVC(l, r, s) \ + s.real = (l.real * r.real + l.imag * r.imag) / (r.real * r.real + r.imag * r.imag), \ + s.imag = (l.imag * r.real - l.real * r.imag) / (r.real * r.real + r.imag * r.imag) +#define MODC(l, r, s) \ + (void)l, (void)r, memset(&s, 0, sizeof(s)), luaL_error(L, "NYI: complex mod") +#define POWC(l, r, s) \ + (void)l, (void)r, memset(&s, 0, sizeof(s)), luaL_error(L, "NYI: complex pow") +#endif + +static int cdata_mul(lua_State* L) { NUMBER_ONLY_BINOP("__mul", MUL, MULC); } + +static int cdata_div(lua_State* L) { NUMBER_ONLY_BINOP("__div", DIV, DIVC); } + +static int cdata_mod(lua_State* L) { NUMBER_ONLY_BINOP("__mod", MOD, MODC); } + +static int cdata_pow(lua_State* L) { NUMBER_ONLY_BINOP("__pow", POW, POWC); } + +#define COMPARE_BINOP(OPSTR, OP, OPC) \ + struct ctype lt, rt; \ + void *lp, *rp; \ + int ret, res; \ + \ + lua_settop(L, 2); \ + \ + lp = to_cdata(L, 1, <); \ + rp = to_cdata(L, 2, &rt); \ + \ + ret = call_user_binop(L, OPSTR, 1, 3, <, 2, 4, &rt); \ + if (ret >= 0) { \ + return ret; \ + } \ + \ + if (IS_COMPLEX(lt.type) || IS_COMPLEX(rt.type)) { \ + complex_double left = check_complex(L, 1, lp, <); \ + complex_double right = check_complex(L, 2, rp, &rt); \ + \ + res = OPC(left, right); \ + \ + lua_pushboolean(L, res); \ + \ + } else { \ + int64_t left = check_intptr(L, 1, lp, <); \ + int64_t right = check_intptr(L, 2, rp, &rt); \ + \ + if (lt.pointers && rt.pointers) { \ + if (is_void_ptr(<) || is_void_ptr(&rt) || \ + is_same_type(L, 3, 4, <, &rt)) { \ + res = OP((uint64_t)left, (uint64_t)right); \ + } else { \ + goto err; \ + } \ + \ + } else if (lt.is_null && rt.type == FUNCTION_PTR_TYPE) { \ + res = OP((uint64_t)left, (uint64_t)right); \ + \ + } else if (rt.is_null && lt.type == FUNCTION_PTR_TYPE) { \ + res = OP((uint64_t)left, (uint64_t)right); \ + \ + } else if (lt.pointers && rt.type == INTPTR_TYPE && rt.is_unsigned) { \ + res = OP((uint64_t)left, (uint64_t)right); \ + \ + } else if (rt.pointers && lt.type == INTPTR_TYPE && lt.is_unsigned) { \ + res = OP((uint64_t)left, (uint64_t)right); \ + \ + } else if (rt.pointers || lt.pointers) { \ + goto err; \ + \ + } else if (lt.is_unsigned && rt.is_unsigned) { \ + res = OP((uint64_t)left, (uint64_t)right); \ + \ + } else if (lt.is_unsigned) { \ + res = OP((int64_t)(uint64_t)left, right); \ + \ + } else if (rt.is_unsigned) { \ + res = OP(left, (int64_t)(uint64_t)right); \ + \ + } else { \ + res = OP(left, right); \ + } \ + \ + lua_pushboolean(L, res); \ + } \ + return 1 + +#define EQ(l, r) (l) == (r) +#define LT(l, r) (l) < (r) +#define LE(l, r) (l) <= (r) + +#ifdef HAVE_COMPLEX +#define EQC(l, r) (l) == (r) +#else +#define EQC(l, r) (l).real == (r).real && (l).imag == (r).imag +#endif + +#define LEC(l, r) EQC(l, r), luaL_error(L, "complex numbers are non-orderable") +#define LTC(l, r) EQC(l, r), luaL_error(L, "complex numbers are non-orderable") + +static int cdata_eq(lua_State* L) { + COMPARE_BINOP("__eq", EQ, EQC); +err: + lua_pushboolean(L, 0); + return 1; +} + +static int cdata_lt(lua_State* L) { + COMPARE_BINOP("__lt", LT, LTC); +err: + lua_getuservalue(L, 1); + lua_getuservalue(L, 2); + push_type_name(L, -2, <); + push_type_name(L, -2, <); + return luaL_error(L, "trying to compare incompatible types %s and %s", + lua_tostring(L, -2), lua_tostring(L, -1)); +} + +static int cdata_le(lua_State* L) { + COMPARE_BINOP("__le", LE, LEC); +err: + lua_getuservalue(L, 1); + lua_getuservalue(L, 2); + push_type_name(L, -2, <); + push_type_name(L, -2, <); + return luaL_error(L, "trying to compare incompatible types %s and %s", + lua_tostring(L, -2), lua_tostring(L, -1)); +} + +static const char* etype_tostring(int type) { + switch (type) { + case VOID_TYPE: + return "void"; + case DOUBLE_TYPE: + return "double"; + case FLOAT_TYPE: + return "float"; + case COMPLEX_DOUBLE_TYPE: + return "complex double"; + case COMPLEX_FLOAT_TYPE: + return "complex float"; + case BOOL_TYPE: + return "bool"; + case INT8_TYPE: + return "int8"; + case INT16_TYPE: + return "int16"; + case INT32_TYPE: + return "int32"; + case INT64_TYPE: + return "int64"; + case INTPTR_TYPE: + return "intptr"; + case ENUM_TYPE: + return "enum"; + case UNION_TYPE: + return "union"; + case STRUCT_TYPE: + return "struct"; + case FUNCTION_PTR_TYPE: + return "function ptr"; + case FUNCTION_TYPE: + return "function"; + default: + return "invalid"; + } +} + +static void print_type(lua_State* L, const struct ctype* ct) { + lua_pushfstring(L, + " sz %d %d %d align %d ptr %d %d %d type %s%s %d %d %d name %d call " + "%d %d var %d %d %d bit %d %d %d %d", + /* sz */ + ct->base_size, ct->array_size, ct->offset, + /* align */ + ct->align_mask, + /* ptr */ + ct->is_array, ct->pointers, ct->const_mask, + /* type */ + ct->is_unsigned ? "u" : "", etype_tostring(ct->type), + ct->is_reference, ct->is_defined, ct->is_null, + /* name */ + ct->has_member_name, + /* call */ + ct->calling_convention, ct->has_var_arg, + /* var */ + ct->is_variable_array, ct->is_variable_struct, + ct->variable_size_known, + /* bit */ + ct->is_bitfield, ct->has_bitfield, ct->bit_offset, ct->bit_size); +} + +static int ctype_tostring(lua_State* L) { + struct ctype ct; + assert(lua_type(L, 1) == LUA_TUSERDATA); + lua_settop(L, 1); + check_ctype(L, 1, &ct); + assert(lua_gettop(L) == 2); + push_type_name(L, -1, &ct); + lua_pushfstring(L, "%s", lua_tostring(L, -1)); + + if (DEBUG_TOSTRING) { + print_type(L, &ct); + lua_concat(L, 2); + } + + return 1; +} + +static int cdata_tostring(lua_State* L) { + struct ctype ct; + char buf[64]; + void* p; + int ret; + + lua_settop(L, 1); + p = to_cdata(L, 1, &ct); + + ret = call_user_op(L, "__tostring", 1, 2, &ct); + if (ret >= 0) { + return ret; + } + + if (ct.pointers > 0 || ct.type == STRUCT_TYPE || ct.type == UNION_TYPE) { + push_type_name(L, -1, &ct); + lua_pushfstring(L, "cdata<%s>: %p", lua_tostring(L, -1), p); + + if (DEBUG_TOSTRING) { + print_type(L, &ct); + lua_concat(L, 2); + } + + return 1; + } + + switch (ct.type) { + case COMPLEX_DOUBLE_TYPE: { + complex_double c = *(complex_double*)p; + if (cimag(c) != 0) { + lua_pushfstring(L, "%f+%fi", creal(c), cimag(c)); + } else { + lua_pushfstring(L, "%f", creal(c)); + } + } + return 1; + + case COMPLEX_FLOAT_TYPE: { + complex_float c = *(complex_float*)p; + if (cimagf(c) != 0) { + lua_pushfstring(L, "%f+%fi", crealf(c), cimagf(c)); + } else { + lua_pushfstring(L, "%f", crealf(c)); + } + } + return 1; + + case FUNCTION_PTR_TYPE: + p = *(void**)p; + push_type_name(L, -1, &ct); + lua_pushfstring(L, "cdata<%s>: %p", lua_tostring(L, -1), *(void**)p); + return 1; + + case INTPTR_TYPE: + lua_pushfstring(L, "%p", *(uintptr_t*)p); + return 1; + + case INT64_TYPE: + sprintf(buf, ct.is_unsigned ? "%" PRIu64 : "%" PRId64, *(uint64_t*)p); + lua_pushstring(L, buf); + return 1; + + default: + sprintf(buf, ct.is_unsigned ? "%" PRId64 : "%" PRId64, + (int64_t)check_intptr(L, 1, p, &ct)); + lua_pushstring(L, buf); + return 1; + } +} + +static int ffi_number(lua_State* L) { + struct ctype ct; + void* data = to_cdata(L, 1, &ct); + + if (ct.type != INVALID_TYPE) { + lua_pushnumber(L, check_intptr(L, 1, data, &ct)); + return 1; + } else { + /* call the old _G.tonumber, we use an upvalue as _G.tonumber is set + * to this function */ + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); + return lua_gettop(L); + } +} + +static int ffi_string(lua_State* L) { + struct ctype ct; + char* data; + lua_settop(L, 2); + + data = (char*)check_cdata(L, 1, &ct); + + if (is_void_ptr(&ct)) { + lua_pushlstring(L, data, (size_t)luaL_checknumber(L, 2)); + return 1; + + } else if (ct.type == INT8_TYPE && ct.pointers == 1) { + size_t sz; + + if (!lua_isnil(L, 2)) { + sz = (size_t)luaL_checknumber(L, 2); + + } else if (ct.is_array && !ct.is_variable_array) { + char* nul = memchr(data, '\0', ct.array_size); + sz = nul ? nul - data : ct.array_size; + + } else { + sz = strlen(data); + } + + lua_pushlstring(L, data, sz); + return 1; + } + + return luaL_error(L, "unable to convert cdata to string"); +} + +static int ffi_copy(lua_State* L) { + struct ctype ft, tt; + char *to, *from; + + setmintop(L, 3); + to = (char*)check_pointer(L, 1, &tt); + from = (char*)check_pointer(L, 2, &ft); + + if (!lua_isnoneornil(L, 3)) { + memcpy(to, from, (size_t)luaL_checknumber(L, 3)); + + } else if (ft.type == INT8_TYPE && ft.pointers == 1) { + size_t sz = ft.is_array ? ft.array_size : strlen(from); + memcpy(to, from, sz); + to[sz] = '\0'; + } + + return 0; +} + +static int ffi_fill(lua_State* L) { + struct ctype ct; + void* to; + size_t sz; + int val = 0; + + setmintop(L, 3); + to = check_pointer(L, 1, &ct); + sz = (size_t)luaL_checknumber(L, 2); + + if (!lua_isnoneornil(L, 3)) { + val = (int)luaL_checkinteger(L, 3); + } + + memset(to, val, sz); + return 0; +} + +static int ffi_abi(lua_State* L) { + luaL_checkstring(L, 1); + push_upval(L, &abi_key); + lua_pushvalue(L, 1); + lua_rawget(L, -2); + lua_pushboolean(L, lua_toboolean(L, -1)); + return 1; +} + +static int ffi_load(lua_State* L) { + const char* libname = luaL_checkstring(L, 1); + void** lib = (void**)lua_newuserdata(L, sizeof(void*)); + + *lib = LoadLibraryA(libname); + +#ifdef LIB_FORMAT_1 + if (!*lib) { + libname = lua_pushfstring(L, LIB_FORMAT_1, lua_tostring(L, 1)); + *lib = LoadLibraryA(libname); + lua_pop(L, 1); + } +#endif + +#ifdef LIB_FORMAT_2 + if (!*lib) { + libname = lua_pushfstring(L, LIB_FORMAT_2, lua_tostring(L, 1)); + *lib = LoadLibraryA(libname); + lua_pop(L, 1); + } +#endif + + if (!*lib) { + return luaL_error(L, "could not load library %s", lua_tostring(L, 1)); + } + + lua_newtable(L); + lua_setuservalue(L, -2); + + push_upval(L, &cmodule_mt_key); + lua_setmetatable(L, -2); + return 1; +} + +static void* find_symbol(lua_State* L, int modidx, const char* asmname) { + size_t i; + void** libs; + size_t num; + void* sym = NULL; + + libs = (void**)lua_touserdata(L, modidx); + num = lua_rawlen(L, modidx) / sizeof(void*); + + for (i = 0; i < num && sym == NULL; i++) { + if (libs[i]) { + sym = GetProcAddressA(libs[i], asmname); + } + } + + return sym; +} + +/* pushes the user table */ +static void* lookup_global(lua_State* L, int modidx, int nameidx, const char** pname, + struct ctype* ct) { + int top = lua_gettop(L); + void* sym; + + modidx = lua_absindex(L, modidx); + nameidx = lua_absindex(L, nameidx); + + *pname = luaL_checkstring(L, nameidx); + + /* get the ctype */ + push_upval(L, &functions_key); + lua_pushvalue(L, nameidx); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + luaL_error(L, "missing declaration for function/global %s", *pname); + return NULL; + } + + /* leave just the ct_usr on the stack */ + *ct = *(const struct ctype*)lua_touserdata(L, -1); + lua_getuservalue(L, -1); + lua_replace(L, top + 1); + lua_pop(L, 1); + + assert(lua_gettop(L) == top + 1); + + /* get the assembly name */ + push_upval(L, &asmname_key); + lua_pushvalue(L, nameidx); + lua_rawget(L, -2); + if (lua_isstring(L, -1)) { + *pname = lua_tostring(L, -1); + } + lua_pop(L, 2); + + sym = find_symbol(L, modidx, *pname); + + assert(lua_gettop(L) == top + 1); + return sym; +} + +static int cmodule_index(lua_State* L) { + const char* asmname; + struct ctype ct; + void* sym; + + lua_settop(L, 2); + + /* see if we have already loaded the function */ + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) { + return 1; + } + lua_pop(L, 2); + + /* check the constants table */ + push_upval(L, &constants_key); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) { + return 1; + } + lua_pop(L, 2); + + /* lookup_global pushes the ct_usr */ + sym = lookup_global(L, 1, 2, &asmname, &ct); + +#if defined _WIN32 && !defined _WIN64 && (defined __i386__ || defined _M_IX86) + if (!sym && ct.type == FUNCTION_TYPE) { + ct.calling_convention = STD_CALL; + lua_pushfstring(L, "_%s@%d", asmname, x86_return_size(L, -1, &ct)); + sym = find_symbol(L, 1, lua_tostring(L, -1)); + lua_pop(L, 1); + } + + if (!sym && ct.type == FUNCTION_TYPE) { + ct.calling_convention = FAST_CALL; + lua_pushfstring(L, "@%s@%d", asmname, x86_return_size(L, -1, &ct)); + sym = find_symbol(L, 1, lua_tostring(L, -1)); + lua_pop(L, 1); + } +#endif + + if (!sym) { + return luaL_error(L, "failed to find function/global %s", asmname); + } + + assert(lua_gettop(L) == 3); /* module, name, ct_usr */ + + if (ct.type == FUNCTION_TYPE) { + assert(!"callbacks are unimplemented"); + } + + /* extern const char* foo; and extern const char foo[]; */ + if (ct.pointers == 1 && ct.type == INT8_TYPE) { + char* str = (char*)sym; + if (!ct.is_array) { + str = *(char**)sym; + } + lua_pushstring(L, str); + return 1; + } + + /* extern struct foo foo[], extern void* foo[]; and extern struct foo foo; */ + if (ct.is_array || + (!ct.pointers && (ct.type == UNION_TYPE || ct.type == STRUCT_TYPE))) { + void* p; + ct.is_reference = 1; + p = push_cdata(L, -1, &ct); + *(void**)p = sym; + return 1; + } + + /* extern void* foo; and extern void (*foo)(); */ + if (ct.pointers || ct.type == FUNCTION_PTR_TYPE) { + void* p = push_cdata(L, -1, &ct); + *(void**)p = *(void**)sym; + return 1; + } + + switch (ct.type) { + case COMPLEX_DOUBLE_TYPE: + case COMPLEX_FLOAT_TYPE: + case INTPTR_TYPE: + case INT64_TYPE: { + /* TODO: complex float/double need to be references if .re and + * .imag are setable */ + void* p = push_cdata(L, -1, &ct); + memcpy(p, sym, ct.base_size); + return 1; + } + + case DOUBLE_TYPE: + lua_pushnumber(L, *(double*)sym); + return 1; + + case FLOAT_TYPE: + lua_pushnumber(L, *(float*)sym); + return 1; + + case BOOL_TYPE: + lua_pushboolean(L, *(bool*)sym); + return 1; + + case INT8_TYPE: + lua_pushnumber(L, ct.is_unsigned ? (lua_Number) * (uint8_t*)sym + : (lua_Number) * (int8_t*)sym); + return 1; + + case INT16_TYPE: + lua_pushnumber(L, ct.is_unsigned ? (lua_Number) * (uint16_t*)sym + : (lua_Number) * (int16_t*)sym); + return 1; + + case INT32_TYPE: + case ENUM_TYPE: + lua_pushnumber(L, ct.is_unsigned ? (lua_Number) * (uint32_t*)sym + : (lua_Number) * (int32_t*)sym); + return 1; + } + + return luaL_error(L, "NYI - global value type"); +} + +static int cmodule_newindex(lua_State* L) { + const char* name; + void* sym; + struct ctype ct; + + lua_settop(L, 3); + + /* pushes the ct_usr */ + sym = lookup_global(L, 1, 2, &name, &ct); + assert(lua_gettop(L) == 4); /* module, name, value, ct_usr */ + + if (sym == NULL) { + return luaL_error(L, "failed to find global %s", name); + } + + if (ct.type == FUNCTION_TYPE || ct.is_array || (ct.const_mask & 1)) { + return luaL_error(L, "can not set global %s", name); + } + + set_value(L, 3, sym, -1, &ct, 1); + return 0; +} + +static const luaL_Reg cdata_mt[] = {{"__gc", &cdata_gc}, + {"__index", &cdata_index}, + {"__newindex", &cdata_newindex}, + {"__add", &cdata_add}, + {"__sub", &cdata_sub}, + {"__mul", &cdata_mul}, + {"__div", &cdata_div}, + {"__mod", &cdata_mod}, + {"__pow", &cdata_pow}, + {"__unm", &cdata_unm}, + {"__eq", &cdata_eq}, + {"__lt", &cdata_lt}, + {"__le", &cdata_le}, + {"__tostring", &cdata_tostring}, + {"__concat", &cdata_concat}, + {"__len", &cdata_len}, + {"__pairs", &cdata_pairs}, + {"__ipairs", &cdata_ipairs}, + {NULL, NULL}}; + +static const luaL_Reg ctype_mt[] = {{"__call", &ctype_call}, + {"__new", &ctype_new}, + {"__tostring", &ctype_tostring}, + {NULL, NULL}}; + +static const luaL_Reg cmodule_mt[] = { + {"__index", &cmodule_index}, {"__newindex", &cmodule_newindex}, {NULL, NULL}}; + +static const luaL_Reg ffi_reg[] = {{"cdef", &ffi_cdef}, {"load", &ffi_load}, + {"new", &ffi_new}, {"typeof", &ffi_typeof}, + {"cast", &ffi_cast}, {"metatype", &ffi_metatype}, + {"gc", &ffi_gc}, {"sizeof", &ffi_sizeof}, + {"alignof", &ffi_alignof}, {"offsetof", &ffi_offsetof}, + {"istype", &ffi_istype}, {"string", &ffi_string}, + {"copy", &ffi_copy}, {"fill", &ffi_fill}, + {"abi", &ffi_abi}, {NULL, NULL}}; + +/* leaves the usr table on the stack */ +static void push_builtin(lua_State* L, struct ctype* ct, const char* name, int type, + int size, int align, int is_unsigned) { + memset(ct, 0, sizeof(*ct)); + ct->type = type; + ct->base_size = size; + ct->align_mask = align; + ct->is_defined = 1; + ct->is_unsigned = is_unsigned; + + push_upval(L, &types_key); + push_ctype(L, 0, ct); + lua_setfield(L, -2, name); + lua_pop(L, 1); /* types */ +} + +static void add_typedef(lua_State* L, const char* from, const char* to) { + struct ctype ct; + struct parser P; + P.line = 1; + P.align_mask = DEFAULT_ALIGN_MASK; + P.next = P.prev = from; + + push_upval(L, &types_key); + parse_type(L, &P, &ct); + parse_argument(L, &P, -1, &ct, NULL, NULL); + push_ctype(L, -1, &ct); + + /* stack is at +4: types, type usr, arg usr, ctype */ + + lua_setfield(L, -4, to); + lua_pop(L, 3); /* types, type usr, arg usr */ +} + +static int setup_upvals(lua_State* L) { + /* setup builtin types */ + { + complex_double* pc; + struct { + char ch; + uint16_t v; + } a16; + struct { + char ch; + uint32_t v; + } a32; + struct { + char ch; + uint64_t v; + } a64; + struct { + char ch; + float v; + } af; + struct { + char ch; + double v; + } ad; +#ifdef HAVE_LONG_DOUBLE + struct { + char ch; + long double v; + } ald; +#endif + struct { + char ch; + uintptr_t v; + } aptr; + struct ctype ct; + struct { + char ch; + complex_float v; + } cf; + struct { + char ch; + complex_double v; + } cd; +#if defined HAVE_LONG_DOUBLE && defined HAVE_COMPLEX + struct { + char ch; + complex long double v; + } cld; +#endif + + push_builtin(L, &ct, "void", VOID_TYPE, 0, 0, 0); + push_builtin(L, &ct, "bool", BOOL_TYPE, sizeof(_Bool), sizeof(_Bool) - 1, 1); + push_builtin(L, &ct, "uint8_t", INT8_TYPE, sizeof(uint8_t), 0, 1); + push_builtin(L, &ct, "int8_t", INT8_TYPE, sizeof(int8_t), 0, 0); + push_builtin(L, &ct, "uint16_t", INT16_TYPE, sizeof(uint16_t), ALIGNOF(a16), 1); + push_builtin(L, &ct, "int16_t", INT16_TYPE, sizeof(int16_t), ALIGNOF(a16), 0); + push_builtin(L, &ct, "uint32_t", INT32_TYPE, sizeof(uint32_t), ALIGNOF(a32), 1); + push_builtin(L, &ct, "int32_t", INT32_TYPE, sizeof(int32_t), ALIGNOF(a32), 0); + push_builtin(L, &ct, "uint64_t", INT64_TYPE, sizeof(uint64_t), ALIGNOF(a64), 1); + push_builtin(L, &ct, "int64_t", INT64_TYPE, sizeof(int64_t), ALIGNOF(a64), 0); + push_builtin(L, &ct, "float", FLOAT_TYPE, sizeof(float), ALIGNOF(af), 0); + push_builtin(L, &ct, "double", DOUBLE_TYPE, sizeof(double), ALIGNOF(ad), 0); +#ifdef HAVE_LONG_DOUBLE + push_builtin(L, &ct, "long double", LONG_DOUBLE_TYPE, sizeof(long double), + ALIGNOF(ald), 0); +#else + push_builtin_undef(L, &ct, "long double", LONG_DOUBLE_TYPE); +#endif + push_builtin(L, &ct, "uintptr_t", INTPTR_TYPE, sizeof(uintptr_t), ALIGNOF(aptr), + 1); + push_builtin(L, &ct, "intptr_t", INTPTR_TYPE, sizeof(uintptr_t), ALIGNOF(aptr), + 0); + push_builtin(L, &ct, "complex float", COMPLEX_FLOAT_TYPE, sizeof(complex_float), + ALIGNOF(cf), 0); + push_builtin(L, &ct, "complex double", COMPLEX_DOUBLE_TYPE, + sizeof(complex_double), ALIGNOF(cd), 0); +#if defined HAVE_LONG_DOUBLE && defined HAVE_COMPLEX + push_builtin(L, &ct, "complex long double", COMPLEX_LONG_DOUBLE_TYPE, + sizeof(complex long double), ALIGNOF(cld), 0); +#else + push_builtin_undef(L, &ct, "complex long double", COMPLEX_LONG_DOUBLE_TYPE); +#endif + + /* add NULL and i constants */ + push_upval(L, &constants_key); + + memset(&ct, 0, sizeof(ct)); + ct.type = VOID_TYPE; + ct.is_defined = 1; + ct.pointers = 1; + ct.is_null = 1; + + push_cdata(L, 0, &ct); + lua_setfield(L, -2, "NULL"); + + memset(&ct, 0, sizeof(ct)); + ct.type = COMPLEX_DOUBLE_TYPE; + ct.is_defined = 1; + ct.base_size = sizeof(complex_double); + pc = (complex_double*)push_cdata(L, 0, &ct); +#ifdef HAVE_COMPLEX + *pc = 1i; +#else + pc->real = 0; + pc->imag = 1; +#endif + lua_setfield(L, -2, "i"); + + lua_pop(L, 1); /* constants */ + } + + assert(lua_gettop(L) == 1); + + /* setup builtin typedefs */ + { + add_typedef(L, "bool", "_Bool"); + + if (sizeof(uint32_t) == sizeof(size_t)) { + add_typedef(L, "uint32_t", "size_t"); + add_typedef(L, "int32_t", "ssize_t"); + } else if (sizeof(uint64_t) == sizeof(size_t)) { + add_typedef(L, "uint64_t", "size_t"); + add_typedef(L, "int64_t", "ssize_t"); + } + + if (sizeof(int32_t) == sizeof(intptr_t)) { + add_typedef(L, "int32_t", "intptr_t"); + add_typedef(L, "int32_t", "ptrdiff_t"); + } else if (sizeof(int64_t) == sizeof(intptr_t)) { + add_typedef(L, "int64_t", "intptr_t"); + add_typedef(L, "int64_t", "ptrdiff_t"); + } + + if (sizeof(uint8_t) == sizeof(wchar_t)) { + add_typedef(L, "uint8_t", "wchar_t"); + } else if (sizeof(uint16_t) == sizeof(wchar_t)) { + add_typedef(L, "uint16_t", "wchar_t"); + } else if (sizeof(uint32_t) == sizeof(wchar_t)) { + add_typedef(L, "uint32_t", "wchar_t"); + } + + if (sizeof(va_list) == sizeof(char*)) { + add_typedef(L, "char*", "va_list"); + } else { + struct { + char ch; + va_list v; + } av; + lua_pushfstring(L, "struct {char data[%d] __attribute__((align(%d)));}", + (int)sizeof(va_list), (int)ALIGNOF(av) + 1); + add_typedef(L, lua_tostring(L, -1), "va_list"); + lua_pop(L, 1); + } + + add_typedef(L, "va_list", "__builtin_va_list"); + add_typedef(L, "va_list", "__gnuc_va_list"); + } + + assert(lua_gettop(L) == 1); + + /* setup ABI params table */ + push_upval(L, &abi_key); + + lua_pop(L, 1); /* abi tbl */ + + /* GC table - shouldn't pin cdata values */ + push_upval(L, &gc_key); + lua_newtable(L); + lua_pushliteral(L, "k"); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L, -2); + lua_pop(L, 1); /* gc table */ + + /* ffi.os */ +#if defined OS_CE + lua_pushliteral(L, "WindowsCE"); +#elif defined OS_WIN + lua_pushliteral(L, "Windows"); +#elif defined OS_OSX + lua_pushliteral(L, "OSX"); +#elif defined OS_LINUX + lua_pushliteral(L, "Linux"); +#elif defined OS_BSD + lua_pushliteral(L, "BSD"); +#elif defined OS_POSIX + lua_pushliteral(L, "POSIX"); +#else + lua_pushliteral(L, "Other"); +#endif + lua_setfield(L, 1, "os"); + + assert(lua_gettop(L) == 1); + + return 0; +} + +static void setup_mt(lua_State* L, const luaL_Reg* mt, int upvals) { + lua_pushboolean(L, 1); + lua_setfield(L, -upvals - 2, "__metatable"); + luaL_setfuncs(L, mt, upvals); +} + +int luaopen_ffi(lua_State* L) { + lua_settop(L, 0); + + lua_newtable(L); + set_upval(L, &niluv_key); + + lua_newtable(L); + setup_mt(L, ctype_mt, 0); + set_upval(L, &ctype_mt_key); + + lua_newtable(L); + set_upval(L, &gc_key); + + lua_newtable(L); + push_upval(L, &gc_key); + setup_mt(L, cdata_mt, 1); + set_upval(L, &cdata_mt_key); + + lua_newtable(L); + setup_mt(L, cmodule_mt, 0); + set_upval(L, &cmodule_mt_key); + + lua_newtable(L); + set_upval(L, &constants_key); + + lua_newtable(L); + set_upval(L, &types_key); + + lua_newtable(L); + set_upval(L, &functions_key); + + lua_newtable(L); + set_upval(L, &asmname_key); + + lua_newtable(L); + set_upval(L, &abi_key); + + lua_pushinteger(L, 1); + set_upval(L, &next_unnamed_key); + + assert(lua_gettop(L) == 0); + + /* ffi table */ + lua_newtable(L); + luaL_setfuncs(L, ffi_reg, 0); + + /* setup_upvals(ffi tbl) */ + lua_pushcfunction(L, &setup_upvals); + lua_pushvalue(L, 1); + lua_call(L, 1, 0); + + assert(lua_gettop(L) == 1); + + lua_getglobal(L, "tonumber"); + lua_pushcclosure(L, &ffi_number, 1); + lua_pushvalue(L, -1); + lua_setglobal(L, "tonumber"); + lua_setfield(L, -2, "number"); /* ffi.number */ + + return 1; +} + +static luaL_Reg preload_list[] = {{"ffi", luaopen_ffi}, {NULL, NULL}}; + +void preload_ffi(lua_State* L) { + lua_getglobal(L, "package"); + lua_getfield(L, -1, "preload"); + luaL_setfuncs(L, preload_list, 0); + lua_pop(L, 2); +} diff --git a/src/tffi.h b/src/tffi.h new file mode 100644 index 00000000..a5fe7f1d --- /dev/null +++ b/src/tffi.h @@ -0,0 +1,375 @@ +/* vim: ts=4 sw=4 sts=4 et tw=78 + * + * Copyright (c) 2011 James R. McKaskill + * + * This software is licensed under the stock MIT license: + * + * 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. + * + * ---------------------------------------------------------------------------- + */ + +#ifndef _tffi_h +#define _tffi_h + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#include +#include +} +#define EXTERN_C extern "C" +#else +#include +#include +#define EXTERN_C extern +#endif + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +#if __STDC_VERSION__ + 0 >= 199901L +#include +#define HAVE_COMPLEX +#define HAVE_LONG_DOUBLE +#endif + +#if defined LUA_FFI_BUILD_AS_DLL +#define EXPORT __declspec(dllexport) +#elif defined __GNUC__ +#define EXPORT __attribute__((visibility("default"))) +#else +#define EXPORT +#endif + +EXTERN_C EXPORT int luaopen_ffi(lua_State* L); + +static int lua_absindex2(lua_State* L, int idx) { + return (LUA_REGISTRYINDEX <= idx && idx < 0) ? lua_gettop(L) + idx + 1 : idx; +} +/* use our own version of lua_absindex such that lua_absindex(L, 0) == 0 */ +#define lua_absindex(L, idx) lua_absindex2(L, idx) + +#if LUA_VERSION_NUM == 501 +#define lua_setuservalue lua_setfenv +#define lua_getuservalue lua_getfenv +#define lua_rawlen lua_objlen +static char* luaL_prepbuffsize(luaL_Buffer* B, size_t sz) { + if (sz > LUAL_BUFFERSIZE) { + luaL_error(B->L, "string too long"); + } + return luaL_prepbuffer(B); +} +#endif + +/* architectures */ +#if defined _WIN32 && defined UNDER_CE +#define OS_CE +#elif defined _WIN32 +#define OS_WIN +#elif defined __APPLE__ && defined __MACH__ +#define OS_OSX +#elif defined __linux__ +#define OS_LINUX +#elif defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__ +#define OS_BSD +#elif defined unix || defined __unix__ || defined __unix || defined _POSIX_VERSION || \ + defined _XOPEN_VERSION +#define OS_POSIX +#endif + +#ifdef _WIN32 + +#ifdef UNDER_CE +static void* DoLoadLibraryA(const char* name) { + wchar_t buf[MAX_PATH]; + int sz = MultiByteToWideChar(CP_UTF8, 0, name, -1, buf, 512); + if (sz > 0) { + buf[sz] = 0; + return LoadLibraryW(buf); + } else { + return NULL; + } +} +#define LoadLibraryA DoLoadLibraryA +#else +#define GetProcAddressA GetProcAddress +#endif + +#define LIB_FORMAT_1 "%s.dll" +#define AllocPage(size) VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE) +#define FreePage(data, size) VirtualFree(data, 0, MEM_RELEASE) +#define EnableExecute(data, size) \ + do { \ + DWORD old; \ + VirtualProtect(data, size, PAGE_EXECUTE, &old); \ + FlushInstructionCache(GetCurrentProcess(), data, size); \ + } while (0) +#define EnableWrite(data, size) \ + do { \ + DWORD old; \ + VirtualProtect(data, size, PAGE_READWRITE, &old); \ + } while (0) + +#else +#define LIB_FORMAT_1 "%s.so" +#define LIB_FORMAT_2 "lib%s.so" +#define LoadLibraryA(name) dlopen(name, RTLD_LAZY | RTLD_GLOBAL) +#define GetProcAddressA(lib, name) dlsym(lib, name) +#define AllocPage(size) \ + mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0) +#define FreePage(data, size) munmap(data, size) +#define EnableExecute(data, size) mprotect(data, size, PROT_READ | PROT_EXEC) +#define EnableWrite(data, size) mprotect(data, size, PROT_READ | PROT_WRITE) +#endif + +#define ALLOW_MISALIGNED_ACCESS + +struct token; + +struct parser { + int line; + const char* next; + const char* prev; + unsigned align_mask; +}; + +#define ALIGN_DOWN(PTR, MASK) (((uintptr_t)(PTR)) & (~((uintptr_t)(MASK)))) +#define ALIGN_UP(PTR, MASK) \ + ((((uintptr_t)(PTR)) + ((uintptr_t)(MASK))) & (~((uintptr_t)(MASK)))) + +/* struct cdata/struct ctype */ + +#define PTR_ALIGN_MASK (sizeof(void*) - 1) +#define FUNCTION_ALIGN_MASK (sizeof(void (*)()) - 1) +#define DEFAULT_ALIGN_MASK 7 + +#ifdef OS_OSX +/* TODO: figure out why the alignof trick doesn't work on OS X */ +#define ALIGNED_DEFAULT 7 +#elif defined __GNUC__ +#define ALIGNED_DEFAULT (__alignof__(void* __attribute__((aligned))) - 1) +#else +#define ALIGNED_DEFAULT PTR_ALIGN_MASK +#endif + +extern int ctype_mt_key; +extern int cdata_mt_key; +extern int cmodule_mt_key; +extern int constants_key; +extern int types_key; +extern int gc_key; +extern int functions_key; +extern int next_unnamed_key; +extern int niluv_key; +extern int asmname_key; + +int equals_upval(lua_State* L, int idx, int* key); +void push_upval(lua_State* L, int* key); +void set_upval(lua_State* L, int* key); + +/* both ctype and cdata are stored as userdatas + * + * usr value is a table shared between the related subtypes which has: + * name -> member ctype (for structs and unions) + * +ves -> member ctype - in memory order (for structs) + * +ves -> argument ctype (for function prototypes) + * 0 -> return ctype (for function prototypes) + * light userdata -> misc + */ + +enum { + C_CALL, + STD_CALL, + FAST_CALL, +}; + +enum { + INVALID_TYPE, + VOID_TYPE, + FLOAT_TYPE, + DOUBLE_TYPE, + LONG_DOUBLE_TYPE, + COMPLEX_FLOAT_TYPE, + COMPLEX_DOUBLE_TYPE, + COMPLEX_LONG_DOUBLE_TYPE, + BOOL_TYPE, + INT8_TYPE, + INT16_TYPE, + INT32_TYPE, + INT64_TYPE, + INTPTR_TYPE, + ENUM_TYPE, + UNION_TYPE, + STRUCT_TYPE, + FUNCTION_TYPE, + FUNCTION_PTR_TYPE, +}; + +#define IS_CHAR_UNSIGNED (((char)-1) > 0) +#define IS_COMPLEX(type) ((type) == COMPLEX_FLOAT_TYPE || (type) == COMPLEX_DOUBLE_TYPE) + +#define POINTER_BITS 2 +#define POINTER_MAX ((1 << POINTER_BITS) - 1) + +#define ALIGNOF(S) ((int)((char*)&S.v - (char*)&S - 1)) + +/* Note: if adding a new member that is associated with a struct/union + * definition then it needs to be copied over in ctype.c:set_defined for when + * we create types based off of the declaration alone. + * + * Since this is used as a header for every ctype and cdata, and we create a + * ton of them on the stack, we try and minimise its size. + */ +struct ctype { + size_t base_size; /* size of the base type in bytes */ + + union { + /* valid if is_bitfield */ + struct { + /* size of bitfield in bits */ + unsigned bit_size : 7; + /* offset within the current byte between 0-63 */ + unsigned bit_offset : 6; + }; + /* Valid if is_array */ + size_t array_size; + /* Valid for is_variable_struct or is_variable_array. If + * variable_size_known (only used for is_variable_struct) then this is + * the total increment otherwise this is the per element increment. + */ + size_t variable_increment; + }; + size_t offset; + unsigned align_mask : 4; /* as (align bytes - 1) eg 7 gives 8 byte alignment */ + unsigned pointers : POINTER_BITS; /* number of dereferences to get to the base type + including +1 for arrays */ + unsigned const_mask : POINTER_MAX + 1; /* const pointer mask, LSB is current pointer, + +1 for the whether the base type is const */ + unsigned type : 5; /* value given by type enum above */ + unsigned is_reference : 1; + unsigned is_array : 1; + unsigned is_defined : 1; + unsigned is_null : 1; + unsigned has_member_name : 1; + unsigned calling_convention : 2; + unsigned has_var_arg : 1; + unsigned is_variable_array : 1; /* set for variable array types where we don't know + the variable size yet */ + unsigned is_variable_struct : 1; + unsigned variable_size_known : 1; /* used for variable structs after we know the + variable size */ + unsigned is_bitfield : 1; + unsigned has_bitfield : 1; + unsigned is_packed : 1; + unsigned is_unsigned : 1; +}; + +#ifdef _MSC_VER +__declspec(align(16)) +#endif + struct cdata { + const struct ctype type +#ifdef __GNUC__ + __attribute__((aligned(16))) +#endif + ; +}; + +typedef void (*cfunction)(void); + +#ifdef HAVE_COMPLEX +typedef double complex complex_double; +typedef float complex complex_float; +#else +typedef struct { + double real, imag; +} complex_double; + +typedef struct { + float real, imag; +} complex_float; + +static double creal(complex_double c) { return c.real; } +static float crealf(complex_float c) { return c.real; } + +static double cimag(complex_double c) { return c.imag; } +static float cimagf(complex_float c) { return c.imag; } +#endif + +void set_defined(lua_State* L, int ct_usr, struct ctype* ct); +struct ctype* push_ctype(lua_State* L, int ct_usr, const struct ctype* ct); +void* push_cdata(lua_State* L, int ct_usr, const struct ctype* ct); /* called from asm */ +void check_ctype(lua_State* L, int idx, struct ctype* ct); +void* to_cdata(lua_State* L, int idx, struct ctype* ct); +void* check_cdata(lua_State* L, int idx, struct ctype* ct); +size_t ctype_size(lua_State* L, const struct ctype* ct); + +int parse_type(lua_State* L, struct parser* P, struct ctype* type); +void parse_argument(lua_State* L, struct parser* P, int ct_usr, struct ctype* type, + struct token* name, struct parser* asmname); +void push_type_name(lua_State* L, int usr, const struct ctype* ct); + +int push_user_mt(lua_State* L, int ct_usr, const struct ctype* ct); + +int ffi_cdef(lua_State* L); + +/* WARNING: assembly needs to be updated for prototype changes of these functions */ +int check_bool(lua_State* L, int idx); +double check_double(lua_State* L, int idx); +double check_complex_imag(lua_State* L, int idx); +float check_float(lua_State* L, int idx); +uint64_t check_uint64(lua_State* L, int idx); +int64_t check_int64(lua_State* L, int idx); +int32_t check_int32(lua_State* L, int idx); +uint32_t check_uint32(lua_State* L, int idx); +uintptr_t check_uintptr(lua_State* L, int idx); +int32_t check_enum(lua_State* L, int idx, int to_usr, const struct ctype* tt); +/* these two will always push a value so that we can create structs/functions on the fly + */ +void* check_typed_pointer(lua_State* L, int idx, int to_usr, const struct ctype* tt); +cfunction check_typed_cfunction(lua_State* L, int idx, int to_usr, + const struct ctype* tt); +complex_double check_complex_double(lua_State* L, int idx); +complex_float check_complex_float(lua_State* L, int idx); + +void unpack_varargs_stack(lua_State* L, int first, int last, char* to); +void unpack_varargs_reg(lua_State* L, int first, int last, char* to); + +void unpack_varargs_stack_skip(lua_State* L, int first, int last, int ints_to_skip, + int floats_to_skip, char* to); +void unpack_varargs_float(lua_State* L, int first, int last, int max, char* to); +void unpack_varargs_int(lua_State* L, int first, int last, int max, char* to); + +#endif diff --git a/src/tffi_ctype.c b/src/tffi_ctype.c new file mode 100644 index 00000000..6b4a25d1 --- /dev/null +++ b/src/tffi_ctype.c @@ -0,0 +1,250 @@ +/* vim: ts=4 sw=4 sts=4 et tw=78 + * Copyright (c) 2011 James R. McKaskill. See license in tffi.h + */ +#include "tffi.h" + +static int to_define_key; + +static void update_on_definition(lua_State* L, int ct_usr, int ct_idx) { + ct_usr = lua_absindex(L, ct_usr); + ct_idx = lua_absindex(L, ct_idx); + + lua_pushlightuserdata(L, &to_define_key); + lua_rawget(L, ct_usr); + + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* pop the nil */ + + /* {} */ + lua_newtable(L); + + /* {__mode='k'} */ + lua_newtable(L); + lua_pushliteral(L, "k"); + lua_setfield(L, -2, "__mode"); + + /* setmetatable({}, {__mode='k'}) */ + lua_setmetatable(L, -2); + + /* usr[TO_UPDATE_KEY] = setmetatable({}, {__mode='k'}) */ + lua_pushlightuserdata(L, &to_define_key); + lua_pushvalue(L, -2); + lua_rawset(L, ct_usr); + + /* leave the table on the stack */ + } + + /* to_update[ctype or cdata] = true */ + lua_pushvalue(L, ct_idx); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + + /* pop the to_update table */ + lua_pop(L, 1); +} + +void set_defined(lua_State* L, int ct_usr, struct ctype* ct) { + ct_usr = lua_absindex(L, ct_usr); + + ct->is_defined = 1; + + /* update ctypes and cdatas that were created before the definition came in */ + lua_pushlightuserdata(L, &to_define_key); + lua_rawget(L, ct_usr); + + if (!lua_isnil(L, -1)) { + lua_pushnil(L); + + while (lua_next(L, -2)) { + struct ctype* upd = (struct ctype*)lua_touserdata(L, -2); + upd->base_size = ct->base_size; + upd->align_mask = ct->align_mask; + upd->is_defined = 1; + upd->is_variable_struct = ct->is_variable_struct; + upd->variable_increment = ct->variable_increment; + assert(!upd->variable_size_known); + lua_pop(L, 1); + } + + lua_pop(L, 1); + /* usr[TO_UPDATE_KEY] = nil */ + lua_pushlightuserdata(L, &to_define_key); + lua_pushnil(L); + lua_rawset(L, ct_usr); + } else { + lua_pop(L, 1); + } +} + +struct ctype* push_ctype(lua_State* L, int ct_usr, const struct ctype* ct) { + struct ctype* ret; + ct_usr = lua_absindex(L, ct_usr); + + ret = (struct ctype*)lua_newuserdata(L, sizeof(struct ctype)); + *ret = *ct; + + push_upval(L, &ctype_mt_key); + lua_setmetatable(L, -2); + +#if LUA_VERSION_NUM == 501 + if (!ct_usr || lua_isnil(L, ct_usr)) { + push_upval(L, &niluv_key); + lua_setfenv(L, -2); + } +#endif + + if (ct_usr && !lua_isnil(L, ct_usr)) { + lua_pushvalue(L, ct_usr); + lua_setuservalue(L, -2); + } + + if (!ct->is_defined && ct_usr && !lua_isnil(L, ct_usr)) { + update_on_definition(L, ct_usr, -1); + } + + return ret; +} + +size_t ctype_size(lua_State* L, const struct ctype* ct) { + if (ct->pointers - ct->is_array) { + return sizeof(void*) * (ct->is_array ? ct->array_size : 1); + + } else if (!ct->is_defined || ct->type == VOID_TYPE) { + return luaL_error(L, "can't calculate size of an undefined type"); + + } else if (ct->variable_size_known) { + assert(ct->is_variable_struct && !ct->is_array); + return ct->base_size + ct->variable_increment; + + } else if (ct->is_variable_array || ct->is_variable_struct) { + return luaL_error(L, + "internal error: calc size of variable type with unknown size"); + + } else { + return ct->base_size * (ct->is_array ? ct->array_size : 1); + } +} + +void* push_cdata(lua_State* L, int ct_usr, const struct ctype* ct) { + struct cdata* cd; + size_t sz = ct->is_reference ? sizeof(void*) : ctype_size(L, ct); + ct_usr = lua_absindex(L, ct_usr); + + /* This is to stop valgrind from complaining. Bitfields are accessed in 8 + * byte chunks so that the code doesn't have to deal with different access + * patterns, but this means that occasionally it will read past the end of + * the struct. As its not setting the bits past the end (only reading and + * then writing the bits back) and the read is aligned its a non-issue, + * but valgrind complains nonetheless. + */ + if (ct->has_bitfield) { + sz = ALIGN_UP(sz, 7); + } + + cd = (struct cdata*)lua_newuserdata(L, sizeof(struct cdata) + sz); + *(struct ctype*)&cd->type = *ct; + memset(cd + 1, 0, sz); + + /* TODO: handle cases where lua_newuserdata returns a pointer that is not + * aligned */ +#if 0 + assert((uintptr_t) (cd + 1) % 8 == 0); +#endif + +#if LUA_VERSION_NUM == 501 + if (!ct_usr || lua_isnil(L, ct_usr)) { + push_upval(L, &niluv_key); + lua_setfenv(L, -2); + } +#endif + + if (ct_usr && !lua_isnil(L, ct_usr)) { + lua_pushvalue(L, ct_usr); + lua_setuservalue(L, -2); + } + + push_upval(L, &cdata_mt_key); + lua_setmetatable(L, -2); + + if (!ct->is_defined && ct_usr && !lua_isnil(L, ct_usr)) { + update_on_definition(L, ct_usr, -1); + } + + return cd + 1; +} + +/* returns the value as a ctype, pushes the user value onto the stack */ +void check_ctype(lua_State* L, int idx, struct ctype* ct) { + if (lua_isstring(L, idx)) { + struct parser P; + P.line = 1; + P.prev = P.next = lua_tostring(L, idx); + P.align_mask = DEFAULT_ALIGN_MASK; + parse_type(L, &P, ct); + parse_argument(L, &P, -1, ct, NULL, NULL); + lua_remove(L, -2); /* remove the user value from parse_type */ + + } else if (lua_getmetatable(L, idx)) { + if (!equals_upval(L, -1, &ctype_mt_key) && !equals_upval(L, -1, &cdata_mt_key)) { + goto err; + } + + lua_pop(L, 1); /* pop the metatable */ + *ct = *(struct ctype*)lua_touserdata(L, idx); + lua_getuservalue(L, idx); + + } else { + goto err; + } + + return; + +err: + luaL_error(L, "expected cdata, ctype or string for arg #%d", idx); +} + +/* to_cdata returns the struct cdata* and pushes the user value onto the + * stack. If the index is not a ctype then ct is not touched, a nil is pushed, + * NULL is returned, and ct->type is set to INVALID_TYPE. Also dereferences + * references */ +void* to_cdata(lua_State* L, int idx, struct ctype* ct) { + struct cdata* cd; + + ct->type = INVALID_TYPE; + if (!lua_isuserdata(L, idx) || !lua_getmetatable(L, idx)) { + lua_pushnil(L); + return NULL; + } + + if (!equals_upval(L, -1, &cdata_mt_key)) { + lua_pop(L, 1); /* mt */ + lua_pushnil(L); + return NULL; + } + + lua_pop(L, 1); /* mt */ + cd = (struct cdata*)lua_touserdata(L, idx); + *ct = cd->type; + lua_getuservalue(L, idx); + + if (ct->is_reference) { + ct->is_reference = 0; + return *(void**)(cd + 1); + + } else if (ct->pointers && !ct->is_array) { + return *(void**)(cd + 1); + + } else { + return cd + 1; + } +} + +/* check_cdata returns the struct cdata* and pushes the user value onto the + * stack. Also dereferences references. */ +void* check_cdata(lua_State* L, int idx, struct ctype* ct) { + void* p = to_cdata(L, idx, ct); + if (ct->type == INVALID_TYPE) { + luaL_error(L, "expected cdata for arg #%d", idx); + } + return p; +} diff --git a/src/tffi_parser.c b/src/tffi_parser.c new file mode 100644 index 00000000..bb860640 --- /dev/null +++ b/src/tffi_parser.c @@ -0,0 +1,2658 @@ +/* vim: ts=4 sw=4 sts=4 et tw=78 + * Copyright (c) 2011 James R. McKaskill. See license in tffi.h + */ +#include "tffi.h" + +#define IS_CONST(tok) \ + (IS_LITERAL(tok, "const") || IS_LITERAL(tok, "__const") || \ + IS_LITERAL(tok, "__const__")) +#define IS_VOLATILE(tok) \ + (IS_LITERAL(tok, "volatile") || IS_LITERAL(tok, "__volatile") || \ + IS_LITERAL(tok, "__volatile__")) +#define IS_RESTRICT(tok) \ + (IS_LITERAL(tok, "restrict") || IS_LITERAL(tok, "__restrict") || \ + IS_LITERAL(tok, "__restrict__")) + +enum etoken { + TOK_NIL, + TOK_NUMBER, + TOK_STRING, + TOK_TOKEN, + + /* the order of these values must match the token strings in lex.c */ + + TOK_3_BEGIN, + TOK_VA_ARG, + + TOK_2_BEGIN, + TOK_LEFT_SHIFT, + TOK_RIGHT_SHIFT, + TOK_LOGICAL_AND, + TOK_LOGICAL_OR, + TOK_LESS_EQUAL, + TOK_GREATER_EQUAL, + TOK_EQUAL, + TOK_NOT_EQUAL, + + TOK_1_BEGIN, + TOK_OPEN_CURLY, + TOK_CLOSE_CURLY, + TOK_SEMICOLON, + TOK_COMMA, + TOK_COLON, + TOK_ASSIGN, + TOK_OPEN_PAREN, + TOK_CLOSE_PAREN, + TOK_OPEN_SQUARE, + TOK_CLOSE_SQUARE, + TOK_DOT, + TOK_AMPERSAND, + TOK_LOGICAL_NOT, + TOK_BITWISE_NOT, + TOK_MINUS, + TOK_PLUS, + TOK_STAR, + TOK_DIVIDE, + TOK_MODULUS, + TOK_LESS, + TOK_GREATER, + TOK_BITWISE_XOR, + TOK_BITWISE_OR, + TOK_QUESTION, + TOK_POUND, + + TOK_REFERENCE = TOK_AMPERSAND, + TOK_MULTIPLY = TOK_STAR, + TOK_BITWISE_AND = TOK_AMPERSAND, +}; + +struct token { + enum etoken type; + int64_t integer; + const char* str; + size_t size; +}; + +#define IS_LITERAL(TOK, STR) \ + (((TOK).size == sizeof(STR) - 1) && 0 == memcmp((TOK).str, STR, sizeof(STR) - 1)) + +/* the order of tokens _must_ match the order of the enum etoken enum */ + +static char tok3[][4] = { + "...", /* unused ">>=", "<<=", */ +}; + +static char tok2[][3] = { + "<<", ">>", "&&", "||", "<=", ">=", "==", "!=", + /* unused "+=", "-=", "*=", "/=", "%=", "&=", "^=", "|=", "++", "--", "->", "::", + */ +}; + +static char tok1[] = {'{', '}', ';', ',', ':', '=', '(', ')', '[', ']', '.', '&', '!', + '~', '-', '+', '*', '/', '%', '<', '>', '^', '|', '?', '#'}; + +static int next_token(lua_State* L, struct parser* P, struct token* tok) { + size_t i; + const char* s = P->next; + + /* UTF8 BOM */ + if (s[0] == '\xEF' && s[1] == '\xBB' && s[2] == '\xBF') { + s += 3; + } + + /* consume whitespace and comments */ + for (;;) { + /* consume whitespace */ + while (*s == '\t' || *s == '\n' || *s == ' ' || *s == '\v' || *s == '\r') { + if (*s == '\n') { + P->line++; + } + s++; + } + + /* consume comments */ + if (*s == '/' && *(s + 1) == '/') { + s = strchr(s, '\n'); + if (!s) { + luaL_error(L, "non-terminated comment"); + } + + } else if (*s == '/' && *(s + 1) == '*') { + s += 2; + + for (;;) { + if (s[0] == '\0') { + luaL_error(L, "non-terminated comment"); + } else if (s[0] == '*' && s[1] == '/') { + s += 2; + break; + } else if (s[0] == '\n') { + P->line++; + } + s++; + } + + } else if (*s == '\0') { + tok->type = TOK_NIL; + return 0; + + } else { + break; + } + } + + P->prev = s; + + for (i = 0; i < sizeof(tok3) / sizeof(tok3[0]); i++) { + if (s[0] == tok3[i][0] && s[1] == tok3[i][1] && s[2] == tok3[i][2]) { + tok->type = (enum etoken)(TOK_3_BEGIN + 1 + i); + P->next = s + 3; + goto end; + } + } + + for (i = 0; i < sizeof(tok2) / sizeof(tok2[0]); i++) { + if (s[0] == tok2[i][0] && s[1] == tok2[i][1]) { + tok->type = (enum etoken)(TOK_2_BEGIN + 1 + i); + P->next = s + 2; + goto end; + } + } + + for (i = 0; i < sizeof(tok1) / sizeof(tok1[0]); i++) { + if (s[0] == tok1[i]) { + tok->type = (enum etoken)(TOK_1_BEGIN + 1 + i); + P->next = s + 1; + goto end; + } + } + + if (*s == '.' || *s == '-' || ('0' <= *s && *s <= '9')) { + /* number */ + tok->type = TOK_NUMBER; + + /* split out the negative case so we get the full range of bits for + * unsigned (eg to support 0xFFFFFFFF where sizeof(long) == 4) + */ + if (*s == '-') { + tok->integer = strtol(s, (char**)&s, 0); + } else { + tok->integer = strtoul(s, (char**)&s, 0); + } + + while (*s == 'u' || *s == 'U' || *s == 'l' || *s == 'L') { + s++; + } + + P->next = s; + goto end; + + } else if (*s == '\'' || *s == '\"') { + /* "..." or '...' */ + char quote = *s; + s++; /* jump over " */ + + tok->type = TOK_STRING; + tok->str = s; + + while (*s != quote) { + if (*s == '\0' || (*s == '\\' && *(s + 1) == '\0')) { + return luaL_error(L, "string not finished"); + } + + if (*s == '\\') { + s++; + } + + s++; + } + + tok->size = s - tok->str; + s++; /* jump over " */ + P->next = s; + goto end; + + } else if (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z') || *s == '_') { + /* tokens */ + tok->type = TOK_TOKEN; + tok->str = s; + + while (('a' <= *s && *s <= 'z') || ('A' <= *s && *s <= 'Z') || *s == '_' || + ('0' <= *s && *s <= '9')) { + s++; + } + + tok->size = s - tok->str; + P->next = s; + goto end; + + } else { + return luaL_error(L, "invalid character %d", P->line); + } + +end: + /*fprintf(stderr, "token %d %d %.*s %.10s\n", tok->type, (int) tok->size, (tok->type + * == TOK_TOKEN || tok->type == TOK_STRING) ? (int) tok->size : 0, tok->str, + * P->next);*/ + return 1; +} + +static void require_token(lua_State* L, struct parser* P, struct token* tok) { + if (!next_token(L, P, tok)) { + luaL_error(L, "unexpected end"); + } +} + +static void check_token(lua_State* L, struct parser* P, int type, const char* str, + const char* err, ...) { + struct token tok; + if (!next_token(L, P, &tok) || tok.type != type || + (tok.type == TOK_TOKEN && + (tok.size != strlen(str) || memcmp(tok.str, str, tok.size) != 0))) { + va_list ap; + va_start(ap, err); + lua_pushvfstring(L, err, ap); + lua_error(L); + } +} + +static void put_back(struct parser* P) { P->next = P->prev; } + +int64_t calculate_constant(lua_State* L, struct parser* P); + +static int g_name_key; +static int g_front_name_key; +static int g_back_name_key; + +#ifndef max +#define max(a, b) ((a) < (b) ? (b) : (a)) +#endif + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +enum test { TEST }; + +/* Parses an enum definition from after the open curly through to the close + * curly. Expects the user table to be on the top of the stack + */ +static int parse_enum(lua_State* L, struct parser* P, struct ctype* type) { + struct token tok; + int value = -1; + int ct_usr = lua_gettop(L); + + for (;;) { + require_token(L, P, &tok); + + assert(lua_gettop(L) == ct_usr); + + if (tok.type == TOK_CLOSE_CURLY) { + break; + } else if (tok.type != TOK_TOKEN) { + return luaL_error(L, "unexpected token in enum at line %d", P->line); + } + + lua_pushlstring(L, tok.str, tok.size); + + require_token(L, P, &tok); + + if (tok.type == TOK_COMMA || tok.type == TOK_CLOSE_CURLY) { + /* we have an auto calculated enum value */ + value++; + } else if (tok.type == TOK_ASSIGN) { + /* we have an explicit enum value */ + value = (int)calculate_constant(L, P); + require_token(L, P, &tok); + } else { + return luaL_error(L, "unexpected token in enum at line %d", P->line); + } + + assert(lua_gettop(L) == ct_usr + 1); + + /* add the enum value to the constants table */ + push_upval(L, &constants_key); + lua_pushvalue(L, -2); + lua_pushnumber(L, value); + lua_rawset(L, -3); + lua_pop(L, 1); + + assert(lua_gettop(L) == ct_usr + 1); + + /* add the enum value to the enum usr value table */ + lua_pushnumber(L, value); + lua_rawset(L, ct_usr); + + if (tok.type == TOK_CLOSE_CURLY) { + break; + } else if (tok.type != TOK_COMMA) { + return luaL_error(L, "unexpected token in enum at line %d", P->line); + } + } + + type->base_size = sizeof(enum test); + type->align_mask = sizeof(enum test) - 1; + + assert(lua_gettop(L) == ct_usr); + return 0; +} + +static void calculate_member_position(lua_State* L, struct parser* P, struct ctype* ct, + struct ctype* mt, int* pbit_offset, + int* pbitfield_type) { + int bit_offset = *pbit_offset; + + if (ct->type == UNION_TYPE) { + size_t msize; + + if (mt->is_variable_struct || mt->is_variable_array) { + luaL_error(L, "NYI: variable sized members in unions"); + return; + + } else if (mt->is_bitfield) { + msize = (mt->align_mask + 1); +#ifdef _WIN32 + /* MSVC has a bug where it doesn't update the alignment of + * a union for bitfield members. */ + mt->align_mask = 0; +#endif + + } else if (mt->is_array) { + msize = mt->array_size * (mt->pointers > 1 ? sizeof(void*) : mt->base_size); + + } else { + msize = mt->pointers ? sizeof(void*) : mt->base_size; + } + + ct->base_size = max(ct->base_size, msize); + + } else if (mt->is_bitfield) { + if (mt->has_member_name && mt->bit_size == 0) { + luaL_error(L, "zero length bitfields must be unnamed on line %d", P->line); + } + +#if defined _WIN32 + /* MSVC uses a seperate storage unit for each size. This is aligned + * before the first bitfield. :0 finishes up the storage unit using + * the greater alignment of the storage unit or the type used with the + * :0. This is equivalent to the :0 always creating a new storage + * unit, but not necesserily using it yet. + */ + + if (*pbitfield_type == -1 && mt->bit_size == 0) { + /* :0 not after a bitfield are ignored */ + return; + } + + { + int different_storage = mt->align_mask != *pbitfield_type; + int no_room_left = + bit_offset + mt->bit_size > (mt->align_mask + 1) * CHAR_BIT; + + if (different_storage || no_room_left || !mt->bit_size) { + ct->base_size += (bit_offset + CHAR_BIT - 1) / CHAR_BIT; + bit_offset = 0; + if (*pbitfield_type >= 0) { + ct->base_size = ALIGN_UP(ct->base_size, *pbitfield_type); + } + ct->base_size = ALIGN_UP(ct->base_size, mt->align_mask); + } + } + + mt->bit_offset = bit_offset; + mt->offset = ct->base_size; + + *pbitfield_type = mt->align_mask; + bit_offset += mt->bit_size; + +#elif defined OS_OSX + /* OSX doesn't use containers and bitfields are not aligned. So + * bitfields never add any padding, except for :0 which still forces + * an alignment based off the type used with the :0 */ + if (mt->bit_size) { + mt->offset = ct->base_size; + mt->bit_offset = bit_offset; + bit_offset += mt->bit_size; + ct->base_size += bit_offset / CHAR_BIT; + bit_offset = bit_offset % CHAR_BIT; + } else { + ct->base_size += (bit_offset + CHAR_BIT - 1) / CHAR_BIT; + ct->base_size = ALIGN_UP(ct->base_size, mt->align_mask); + bit_offset = 0; + } + + if (!mt->has_member_name) { + /* unnamed bitfields don't update the struct alignment */ + mt->align_mask = 0; + } + +#elif defined __GNUC__ + /* GCC tries to pack bitfields in as close as much as possible, but + * still making sure that they don't cross alignment boundaries. + * :0 forces an alignment based off the type used with the :0 + */ + + int bits_used = + (ct->base_size - ALIGN_DOWN(ct->base_size, mt->align_mask)) * CHAR_BIT + + bit_offset; + int need_to_realign = bits_used + mt->bit_size > mt->base_size * CHAR_BIT; + + if (!mt->is_packed && (!mt->bit_size || need_to_realign)) { + ct->base_size += (bit_offset + CHAR_BIT - 1) / CHAR_BIT; + ct->base_size = ALIGN_UP(ct->base_size, mt->align_mask); + bit_offset = 0; + } + + mt->bit_offset = bit_offset; + mt->offset = ct->base_size; + + bit_offset += mt->bit_size; + ct->base_size += bit_offset / CHAR_BIT; + bit_offset = bit_offset % CHAR_BIT; + + /* unnamed bitfields don't update the struct alignment */ + if (!mt->has_member_name) { + mt->align_mask = 0; + } +#else +#error +#endif + + } else { + /* finish up the current bitfield storage unit */ + ct->base_size += (bit_offset + CHAR_BIT - 1) / CHAR_BIT; + bit_offset = 0; + + if (*pbitfield_type >= 0) { + ct->base_size = ALIGN_UP(ct->base_size, *pbitfield_type); + } + + *pbitfield_type = -1; + + ct->base_size = ALIGN_UP(ct->base_size, mt->align_mask); + mt->offset = ct->base_size; + + if (mt->is_variable_array) { + ct->is_variable_struct = 1; + ct->variable_increment = mt->pointers > 1 ? sizeof(void*) : mt->base_size; + + } else if (mt->is_variable_struct) { + assert(!mt->variable_size_known && !mt->is_array && !mt->pointers); + ct->base_size += mt->base_size; + ct->is_variable_struct = 1; + ct->variable_increment = mt->variable_increment; + + } else if (mt->is_array) { + ct->base_size += + mt->array_size * (mt->pointers > 1 ? sizeof(void*) : mt->base_size); + + } else { + ct->base_size += mt->pointers ? sizeof(void*) : mt->base_size; + } + } + + /* increase the outer struct/union alignment if needed */ + if (mt->align_mask > (int)ct->align_mask) { + ct->align_mask = mt->align_mask; + } + + if (mt->has_bitfield || mt->is_bitfield) { + ct->has_bitfield = 1; + } + + *pbit_offset = bit_offset; +} + +static int copy_submembers(lua_State* L, int to_usr, int from_usr, const struct ctype* ft, + int* midx) { + struct ctype ct; + int i, sublen; + + from_usr = lua_absindex(L, from_usr); + to_usr = lua_absindex(L, to_usr); + + /* integer keys */ + sublen = (int)lua_rawlen(L, from_usr); + for (i = 1; i <= sublen; i++) { + lua_rawgeti(L, from_usr, i); + + ct = *(const struct ctype*)lua_touserdata(L, -1); + ct.offset += ft->offset; + lua_getuservalue(L, -1); + + push_ctype(L, -1, &ct); + lua_rawseti(L, to_usr, (*midx)++); + + lua_pop(L, 2); /* ctype, user value */ + } + + /* string keys */ + lua_pushnil(L); + while (lua_next(L, from_usr)) { + if (lua_type(L, -2) == LUA_TSTRING) { + struct ctype ct = *(const struct ctype*)lua_touserdata(L, -1); + ct.offset += ft->offset; + lua_getuservalue(L, -1); + + /* uservalue[sub_mname] = new_sub_mtype */ + lua_pushvalue(L, -3); + push_ctype(L, -2, &ct); + lua_rawset(L, to_usr); + + lua_pop(L, 1); /* remove submember user value */ + } + lua_pop(L, 1); + } + + return 0; +} + +static int add_member(lua_State* L, int ct_usr, int mname, int mbr_usr, + const struct ctype* mt, int* midx) { + ct_usr = lua_absindex(L, ct_usr); + mname = lua_absindex(L, mname); + + push_ctype(L, mbr_usr, mt); + + /* usrvalue[mbr index] = pushed mtype */ + lua_pushvalue(L, -1); + lua_rawseti(L, ct_usr, (*midx)++); + + /* set usrvalue[mname] = pushed mtype */ + lua_pushvalue(L, mname); + lua_pushvalue(L, -2); + lua_rawset(L, ct_usr); + + /* set usrvalue[mtype] = mname */ + lua_pushvalue(L, -1); + lua_pushvalue(L, mname); + lua_rawset(L, ct_usr); + + lua_pop(L, 1); + + return 0; +} + +/* Parses a struct from after the open curly through to the close curly. + */ +static int parse_struct(lua_State* L, struct parser* P, int tmp_usr, + const struct ctype* ct) { + struct token tok; + int midx = 1; + int top = lua_gettop(L); + + tmp_usr = lua_absindex(L, tmp_usr); + + /* parse members */ + for (;;) { + struct ctype mbase; + + assert(lua_gettop(L) == top); + + /* see if we're at the end of the struct */ + require_token(L, P, &tok); + if (tok.type == TOK_CLOSE_CURLY) { + break; + } else if (ct->is_variable_struct) { + return luaL_error( + L, "can't have members after a variable sized member on line %d", + P->line); + } else { + put_back(P); + } + + /* members are of the form + * , , ; + * eg struct foo bar, *bar2[2]; + * mbase is 'struct foo' + * mtype is '' then '*[2]' + * mname is 'bar' then 'bar2' + */ + + parse_type(L, P, &mbase); + + for (;;) { + struct token mname; + struct ctype mt = mbase; + + memset(&mname, 0, sizeof(mname)); + + if (ct->is_variable_struct) { + return luaL_error( + L, "can't have members after a variable sized member on line %d", + P->line); + } + + assert(lua_gettop(L) == top + 1); + parse_argument(L, P, -1, &mt, &mname, NULL); + assert(lua_gettop(L) == top + 2); + + if (!mt.is_defined && (mt.pointers - mt.is_array) == 0) { + return luaL_error(L, "member type is undefined on line %d", P->line); + } + + if (mt.type == VOID_TYPE && (mt.pointers - mt.is_array) == 0) { + return luaL_error(L, "member type can not be void on line %d", P->line); + } + + mt.has_member_name = (mname.size > 0); + lua_pushlstring(L, mname.str, mname.size); + + add_member(L, tmp_usr, -1, -2, &mt, &midx); + + /* pop the usr value from push_argument and the member name */ + lua_pop(L, 2); + assert(lua_gettop(L) == top + 1); + + require_token(L, P, &tok); + if (tok.type == TOK_SEMICOLON) { + break; + } else if (tok.type != TOK_COMMA) { + luaL_error(L, "unexpected token in struct definition on line %d", + P->line); + } + } + + /* pop the usr value from push_type */ + lua_pop(L, 1); + } + + assert(lua_gettop(L) == top); + return 0; +} + +static int calculate_struct_offsets(lua_State* L, struct parser* P, int ct_usr, + struct ctype* ct, int tmp_usr) { + int i; + int midx = 1; + int sz = (int)lua_rawlen(L, tmp_usr); + int bit_offset = 0; + int bitfield_type = -1; + + ct_usr = lua_absindex(L, ct_usr); + tmp_usr = lua_absindex(L, tmp_usr); + + for (i = 1; i <= sz; i++) { + struct ctype mt; + + /* get the member type */ + lua_rawgeti(L, tmp_usr, i); + mt = *(const struct ctype*)lua_touserdata(L, -1); + + /* get the member user table */ + lua_getuservalue(L, -1); + + /* get the member name */ + lua_pushvalue(L, -2); + lua_rawget(L, tmp_usr); + + calculate_member_position(L, P, ct, &mt, &bit_offset, &bitfield_type); + + if (mt.has_member_name) { + assert(!lua_isnil(L, -1)); + add_member(L, ct_usr, -1, -2, &mt, &midx); + + } else if (mt.type == STRUCT_TYPE || mt.type == UNION_TYPE) { + /* With an unnamed member we copy all of the submembers into our + * usr value adjusting the offset as necessary. Note ctypes are + * immutable so need to push a new ctype to update the offset. + */ + copy_submembers(L, ct_usr, -2, &mt, &midx); + + } else { + /* We ignore unnamed members that aren't structs or unions. These + * are there just to change the padding */ + } + + lua_pop(L, 3); + } + + /* finish up the current bitfield storage unit */ + ct->base_size += (bit_offset + CHAR_BIT - 1) / CHAR_BIT; + + /* only void is allowed 0 size */ + if (ct->base_size == 0) { + ct->base_size = 1; + } + + ct->base_size = ALIGN_UP(ct->base_size, ct->align_mask); + return 0; +} + +/* copy over attributes that could be specified before the typedef eg + * __attribute__(packed) const type_t */ +static void instantiate_typedef(struct parser* P, struct ctype* tt, + const struct ctype* ft) { + struct ctype pt = *tt; + *tt = *ft; + + tt->const_mask |= pt.const_mask; + tt->is_packed = pt.is_packed; + + if (tt->is_packed) { + tt->align_mask = 0; + } else { + /* Instantiate the typedef in the current packing. This may be + * further updated if a pointer is added or another alignment + * attribute is applied. If pt.align_mask is already non-zero than an + * increased alignment via __declspec(aligned(#)) has been set. */ + tt->align_mask = max(min(P->align_mask, tt->align_mask), pt.align_mask); + } +} + +/* this parses a struct or union starting with the optional + * name before the opening brace + * leaves the type usr value on the stack + */ +static int parse_record(lua_State* L, struct parser* P, struct ctype* ct) { + struct token tok; + int top = lua_gettop(L); + + require_token(L, P, &tok); + + /* name is optional */ + if (tok.type == TOK_TOKEN) { + /* declaration */ + lua_pushlstring(L, tok.str, tok.size); + + assert(lua_gettop(L) == top + 1); + + /* lookup the name to see if we've seen this type before */ + push_upval(L, &types_key); + lua_pushvalue(L, -2); + lua_rawget(L, top + 2); + + assert(lua_gettop(L) == top + 3); + + if (lua_isnil(L, -1)) { + lua_pop(L, 1); /* pop the nil usr value */ + lua_newtable(L); /* the new usr table */ + + /* stack layout is: + * top+1: record name + * top+2: types table + * top+3: new usr table + */ + + lua_pushlightuserdata(L, &g_name_key); + lua_pushvalue(L, top + 1); + lua_rawset(L, top + 3); /* usr[name_key] = name */ + + lua_pushvalue(L, top + 1); + push_ctype(L, top + 3, ct); + lua_rawset(L, top + 2); /* types[name] = new_ctype */ + + } else { + /* get the exsting declared type */ + const struct ctype* prevt = (const struct ctype*)lua_touserdata(L, top + 3); + + if (prevt->type != ct->type) { + lua_getuservalue(L, top + 3); + push_type_name(L, -1, ct); + push_type_name(L, top + 3, prevt); + luaL_error(L, "type '%s' previously declared as '%s'", + lua_tostring(L, -2), lua_tostring(L, -1)); + } + + instantiate_typedef(P, ct, prevt); + + /* replace the ctype with its usr value */ + lua_getuservalue(L, -1); + lua_replace(L, -2); + } + + /* remove the extra name and types table */ + lua_replace(L, -3); + lua_pop(L, 1); + + assert(lua_gettop(L) == top + 1 && lua_istable(L, -1)); + + /* if a name is given then we may be at the end of the string + * eg for ffi.new('struct foo') + */ + if (!next_token(L, P, &tok)) { + return 0; + } + + } else { + /* create a new unnamed record */ + int num; + + /* get the next unnamed number */ + push_upval(L, &next_unnamed_key); + num = lua_tointeger(L, -1); + lua_pop(L, 1); + + /* increment the unnamed upval */ + lua_pushinteger(L, num + 1); + set_upval(L, &next_unnamed_key); + + lua_newtable(L); /* the new usr table - leave on stack */ + + /* usr[name_key] = num */ + lua_pushlightuserdata(L, &g_name_key); + lua_pushfstring(L, "%d", num); + lua_rawset(L, -3); + } + + if (tok.type != TOK_OPEN_CURLY) { + /* this may just be a declaration or use of the type as an argument or + * member */ + put_back(P); + return 0; + } + + if (ct->is_defined) { + return luaL_error(L, "redefinition in line %d", P->line); + } + + assert(lua_gettop(L) == top + 1 && lua_istable(L, -1)); + + if (ct->type == ENUM_TYPE) { + parse_enum(L, P, ct); + } else { + /* we do a two stage parse, where we parse the content first and build up + * the temp user table. We then iterate over that to calculate the offsets + * and fill out ct_usr. This is so we can handle out of order members + * (eg vtable) and attributes specified at the end of the struct. + */ + lua_newtable(L); + parse_struct(L, P, -1, ct); + calculate_struct_offsets(L, P, -2, ct, -1); + assert(lua_gettop(L) == top + 2 && lua_istable(L, -1)); + lua_pop(L, 1); + } + + assert(lua_gettop(L) == top + 1 && lua_istable(L, -1)); + set_defined(L, -1, ct); + assert(lua_gettop(L) == top + 1); + return 0; +} + +/* parses single or multi work built in types, and pushes it onto the stack */ +static int parse_type_name(lua_State* L, struct parser* P) { + struct token tok; + int flags = 0; + + enum { + UNSIGNED = 0x01, + SIGNED = 0x02, + LONG = 0x04, + SHORT = 0x08, + INT = 0x10, + CHAR = 0x20, + LONG_LONG = 0x40, + INT8 = 0x80, + INT16 = 0x100, + INT32 = 0x200, + INT64 = 0x400, + DOUBLE = 0x800, + FLOAT = 0x1000, + COMPLEX = 0x2000, + }; + + require_token(L, P, &tok); + + /* we have to manually decode the builtin types since they can take up + * more then one token + */ + for (;;) { + if (tok.type != TOK_TOKEN) { + break; + } else if (IS_LITERAL(tok, "unsigned")) { + flags |= UNSIGNED; + } else if (IS_LITERAL(tok, "signed")) { + flags |= SIGNED; + } else if (IS_LITERAL(tok, "short")) { + flags |= SHORT; + } else if (IS_LITERAL(tok, "char")) { + flags |= CHAR; + } else if (IS_LITERAL(tok, "long")) { + flags |= (flags & LONG) ? LONG_LONG : LONG; + } else if (IS_LITERAL(tok, "int")) { + flags |= INT; + } else if (IS_LITERAL(tok, "__int8")) { + flags |= INT8; + } else if (IS_LITERAL(tok, "__int16")) { + flags |= INT16; + } else if (IS_LITERAL(tok, "__int32")) { + flags |= INT32; + } else if (IS_LITERAL(tok, "__int64")) { + flags |= INT64; + } else if (IS_LITERAL(tok, "double")) { + flags |= DOUBLE; + } else if (IS_LITERAL(tok, "float")) { + flags |= FLOAT; + } else if (IS_LITERAL(tok, "complex") || IS_LITERAL(tok, "_Complex")) { + flags |= COMPLEX; + } else if (IS_LITERAL(tok, "register")) { + /* ignore */ + } else { + break; + } + + if (!next_token(L, P, &tok)) { + break; + } + } + + if (flags) { + put_back(P); + } + + if (flags & CHAR) { + if (flags & SIGNED) { + lua_pushliteral(L, "int8_t"); + } else if (flags & UNSIGNED) { + lua_pushliteral(L, "uint8_t"); + } else { + lua_pushstring(L, (((char)-1) > 0) ? "uint8_t" : "int8_t"); + } + + } else if (flags & INT8) { + lua_pushstring(L, (flags & UNSIGNED) ? "uint8_t" : "int8_t"); + } else if (flags & INT16) { + lua_pushstring(L, (flags & UNSIGNED) ? "uint16_t" : "int16_t"); + } else if (flags & INT32) { + lua_pushstring(L, (flags & UNSIGNED) ? "uint32_t" : "int32_t"); + } else if (flags & (INT64 | LONG_LONG)) { + lua_pushstring(L, (flags & UNSIGNED) ? "uint64_t" : "int64_t"); + + } else if (flags & COMPLEX) { + if (flags & LONG) { + lua_pushliteral(L, "complex long double"); + } else if (flags & FLOAT) { + lua_pushliteral(L, "complex float"); + } else { + lua_pushliteral(L, "complex double"); + } + + } else if (flags & DOUBLE) { + if (flags & LONG) { + lua_pushliteral(L, "long double"); + } else { + lua_pushliteral(L, "double"); + } + + } else if (flags & FLOAT) { + lua_pushliteral(L, "float"); + + } else if (flags & SHORT) { +#define SHORT_TYPE(u) \ + (sizeof(short) == sizeof(int64_t) \ + ? u "int64_t" \ + : sizeof(short) == sizeof(int32_t) ? u "int32_t" : u "int16_t") + if (flags & UNSIGNED) { + lua_pushstring(L, SHORT_TYPE("u")); + } else { + lua_pushstring(L, SHORT_TYPE("")); + } +#undef SHORT_TYPE + + } else if (flags & LONG) { +#define LONG_TYPE(u) (sizeof(long) == sizeof(int64_t) ? u "int64_t" : u "int32_t") + if (flags & UNSIGNED) { + lua_pushstring(L, LONG_TYPE("u")); + } else { + lua_pushstring(L, LONG_TYPE("")); + } +#undef LONG_TYPE + + } else if (flags) { +#define INT_TYPE(u) \ + (sizeof(int) == sizeof(int64_t) \ + ? u "int64_t" \ + : sizeof(int) == sizeof(int32_t) ? u "int32_t" : u "int16_t") + if (flags & UNSIGNED) { + lua_pushstring(L, INT_TYPE("u")); + } else { + lua_pushstring(L, INT_TYPE("")); + } +#undef INT_TYPE + + } else { + lua_pushlstring(L, tok.str, tok.size); + } + + return 0; +} + +/* parse_attribute parses a token to see if it is an attribute. It may then + * parse some following tokens to decode the attribute setting the appropriate + * fields in ct. It will return 1 if the token was used (and possibly some + * more following it) or 0 if not. If the token was used, the next token must + * be retrieved using next_token/require_token. + */ +static int parse_attribute(lua_State* L, struct parser* P, struct token* tok, + struct ctype* ct, struct parser* asmname) { + if (tok->type != TOK_TOKEN) { + return 0; + + } else if (asmname && (IS_LITERAL(*tok, "__asm__") || IS_LITERAL(*tok, "__asm"))) { + check_token(L, P, TOK_OPEN_PAREN, NULL, + "unexpected token after __asm__ on line %d", P->line); + *asmname = *P; + + require_token(L, P, tok); + while (tok->type == TOK_STRING) { + require_token(L, P, tok); + } + + if (tok->type != TOK_CLOSE_PAREN) { + luaL_error(L, "unexpected token after __asm__ on line %d", P->line); + } + return 1; + + } else if (IS_LITERAL(*tok, "__attribute__") || IS_LITERAL(*tok, "__declspec")) { + int parens = 1; + check_token(L, P, TOK_OPEN_PAREN, NULL, + "expected parenthesis after __attribute__ or __declspec on line %d", + P->line); + + for (;;) { + require_token(L, P, tok); + if (tok->type == TOK_OPEN_PAREN) { + parens++; + } else if (tok->type == TOK_CLOSE_PAREN) { + if (--parens == 0) { + break; + } + + } else if (tok->type != TOK_TOKEN) { + /* ignore unknown symbols within parentheses */ + + } else if (IS_LITERAL(*tok, "align") || IS_LITERAL(*tok, "aligned") || + IS_LITERAL(*tok, "__aligned__")) { + unsigned align = 0; + require_token(L, P, tok); + + switch (tok->type) { + case TOK_CLOSE_PAREN: + align = ALIGNED_DEFAULT; + put_back(P); + break; + + case TOK_OPEN_PAREN: + require_token(L, P, tok); + + if (tok->type != TOK_NUMBER) { + luaL_error(L, "expected align(#) on line %d", P->line); + } + + switch (tok->integer) { + case 1: + align = 0; + break; + case 2: + align = 1; + break; + case 4: + align = 3; + break; + case 8: + align = 7; + break; + case 16: + align = 15; + break; + default: + luaL_error(L, "unsupported align size on line %d", + P->line); + } + + check_token(L, P, TOK_CLOSE_PAREN, NULL, + "expected align(#) on line %d", P->line); + break; + + default: + luaL_error(L, "expected align(#) on line %d", P->line); + } + + /* __attribute__(aligned(#)) is only supposed to increase alignment */ + ct->align_mask = max(align, ct->align_mask); + + } else if (IS_LITERAL(*tok, "packed") || IS_LITERAL(*tok, "__packed__")) { + ct->align_mask = 0; + ct->is_packed = 1; + + } else if (IS_LITERAL(*tok, "mode") || IS_LITERAL(*tok, "__mode__")) { + check_token(L, P, TOK_OPEN_PAREN, NULL, "expected mode(MODE) on line %d", + P->line); + + require_token(L, P, tok); + if (tok->type != TOK_TOKEN) { + luaL_error(L, "expected mode(MODE) on line %d", P->line); + } + + if (ct->type == FLOAT_TYPE || ct->type == DOUBLE_TYPE) { + struct { + char ch; + float v; + } af; + struct { + char ch; + double v; + } ad; + + if (IS_LITERAL(*tok, "SF") || IS_LITERAL(*tok, "__SF__")) { + ct->type = FLOAT_TYPE; + ct->base_size = sizeof(float); + ct->align_mask = ALIGNOF(af); + + } else if (IS_LITERAL(*tok, "DF") || IS_LITERAL(*tok, "__DF__")) { + ct->type = DOUBLE_TYPE; + ct->base_size = sizeof(double); + ct->align_mask = ALIGNOF(ad); + + } else { + luaL_error(L, "unexpected mode on line %d", P->line); + } + + } else { + struct { + char ch; + uint16_t v; + } a16; + struct { + char ch; + uint32_t v; + } a32; + struct { + char ch; + uint64_t v; + } a64; + + if (IS_LITERAL(*tok, "QI") || IS_LITERAL(*tok, "__QI__") || + IS_LITERAL(*tok, "byte") || IS_LITERAL(*tok, "__byte__")) { + ct->type = INT8_TYPE; + ct->base_size = sizeof(uint8_t); + ct->align_mask = 0; + + } else if (IS_LITERAL(*tok, "HI") || IS_LITERAL(*tok, "__HI__")) { + ct->type = INT16_TYPE; + ct->base_size = sizeof(uint16_t); + ct->align_mask = ALIGNOF(a16); + + } else if (IS_LITERAL(*tok, "SI") || IS_LITERAL(*tok, "__SI__")) { + ct->type = INT32_TYPE; + ct->base_size = sizeof(uint32_t); + ct->align_mask = ALIGNOF(a32); + + } else if (IS_LITERAL(*tok, "DI") || IS_LITERAL(*tok, "__DI__")) { + ct->type = INT64_TYPE; + ct->base_size = sizeof(uint64_t); + ct->align_mask = ALIGNOF(a64); + + } else { + luaL_error(L, "unexpected mode on line %d", P->line); + } + } + + check_token(L, P, TOK_CLOSE_PAREN, NULL, "expected mode(MODE) on line %d", + P->line); + + } else if (IS_LITERAL(*tok, "cdecl") || IS_LITERAL(*tok, "__cdecl__")) { + ct->calling_convention = C_CALL; + + } else if (IS_LITERAL(*tok, "fastcall") || IS_LITERAL(*tok, "__fastcall__")) { + ct->calling_convention = FAST_CALL; + + } else if (IS_LITERAL(*tok, "stdcall") || IS_LITERAL(*tok, "__stdcall__")) { + ct->calling_convention = STD_CALL; + } + /* ignore unknown tokens within parentheses */ + } + return 1; + + } else if (IS_LITERAL(*tok, "__cdecl")) { + ct->calling_convention = C_CALL; + return 1; + + } else if (IS_LITERAL(*tok, "__fastcall")) { + ct->calling_convention = FAST_CALL; + return 1; + + } else if (IS_LITERAL(*tok, "__stdcall")) { + ct->calling_convention = STD_CALL; + return 1; + + } else if (IS_LITERAL(*tok, "__extension__") || IS_LITERAL(*tok, "extern")) { + /* ignore */ + return 1; + + } else { + return 0; + } +} + +/* parses out the base type of a type expression in a function declaration, + * struct definition, typedef etc + * + * leaves the usr value of the type on the stack + */ +int parse_type(lua_State* L, struct parser* P, struct ctype* ct) { + struct token tok; + int top = lua_gettop(L); + + memset(ct, 0, sizeof(*ct)); + + require_token(L, P, &tok); + + /* get function attributes before the return type */ + while (parse_attribute(L, P, &tok, ct, NULL)) { + require_token(L, P, &tok); + } + + /* get const/volatile before the base type */ + for (;;) { + if (tok.type != TOK_TOKEN) { + return luaL_error(L, "unexpected value before type name on line %d", P->line); + + } else if (IS_CONST(tok)) { + ct->const_mask = 1; + require_token(L, P, &tok); + + } else if (IS_VOLATILE(tok) || IS_RESTRICT(tok)) { + /* ignored for now */ + require_token(L, P, &tok); + + } else { + break; + } + } + + /* get base type */ + if (tok.type != TOK_TOKEN) { + return luaL_error(L, "unexpected value before type name on line %d", P->line); + + } else if (IS_LITERAL(tok, "struct")) { + ct->type = STRUCT_TYPE; + parse_record(L, P, ct); + + } else if (IS_LITERAL(tok, "union")) { + ct->type = UNION_TYPE; + parse_record(L, P, ct); + + } else if (IS_LITERAL(tok, "enum")) { + ct->type = ENUM_TYPE; + parse_record(L, P, ct); + + } else { + put_back(P); + + /* lookup type */ + push_upval(L, &types_key); + parse_type_name(L, P); + lua_rawget(L, -2); + lua_remove(L, -2); + + if (lua_isnil(L, -1)) { + lua_pushlstring(L, tok.str, tok.size); + return luaL_error(L, "unknown type %s on line %d", lua_tostring(L, -1), + P->line); + } + + instantiate_typedef(P, ct, (const struct ctype*)lua_touserdata(L, -1)); + + /* we only want the usr tbl from the ctype in the types tbl */ + lua_getuservalue(L, -1); + lua_replace(L, -2); + } + + while (next_token(L, P, &tok)) { + if (tok.type != TOK_TOKEN) { + put_back(P); + break; + + } else if (IS_CONST(tok) || IS_VOLATILE(tok)) { + /* ignore for now */ + + } else { + put_back(P); + break; + } + } + + assert(lua_gettop(L) == top + 1 && (lua_istable(L, -1) || lua_isnil(L, -1))); + return 0; +} + +enum name_type { + BOTH, + FRONT, + BACK, +}; + +static void append_type_name(luaL_Buffer* B, int usr, const struct ctype* ct, + enum name_type type) { + size_t i; + lua_State* L = B->L; + + usr = lua_absindex(L, usr); + + if (type == FRONT || type == BOTH) { + if (ct->type != FUNCTION_PTR_TYPE && (ct->const_mask & (1 << ct->pointers))) { + luaL_addstring(B, "const "); + } + + if (ct->is_unsigned) { + luaL_addstring(B, "unsigned "); + } + + switch (ct->type) { + case ENUM_TYPE: + luaL_addstring(B, "enum "); + goto get_name; + + case STRUCT_TYPE: + luaL_addstring(B, "struct "); + goto get_name; + + case UNION_TYPE: + luaL_addstring(B, "union "); + goto get_name; + + get_name: + lua_pushlightuserdata(L, &g_name_key); + lua_rawget(L, usr); + luaL_addvalue(B); + break; + + case FUNCTION_TYPE: + case FUNCTION_PTR_TYPE: + lua_pushlightuserdata(L, &g_front_name_key); + lua_rawget(L, usr); + luaL_addvalue(B); + break; + + case VOID_TYPE: + luaL_addstring(B, "void"); + break; + case BOOL_TYPE: + luaL_addstring(B, "bool"); + break; + case DOUBLE_TYPE: + luaL_addstring(B, "double"); + break; + case LONG_DOUBLE_TYPE: + luaL_addstring(B, "long double"); + break; + case FLOAT_TYPE: + luaL_addstring(B, "float"); + break; + case COMPLEX_LONG_DOUBLE_TYPE: + luaL_addstring(B, "long complex double"); + break; + case COMPLEX_DOUBLE_TYPE: + luaL_addstring(B, "complex double"); + break; + case COMPLEX_FLOAT_TYPE: + luaL_addstring(B, "complex float"); + break; + case INT8_TYPE: + luaL_addstring(B, "char"); + break; + case INT16_TYPE: + luaL_addstring(B, "short"); + break; + case INT32_TYPE: + luaL_addstring(B, "int"); + break; + case INT64_TYPE: + luaL_addstring(B, "long long"); + break; + + case INTPTR_TYPE: + if (sizeof(intptr_t) == sizeof(int32_t)) { + luaL_addstring(B, "long"); + } else if (sizeof(intptr_t) == sizeof(int64_t)) { + luaL_addstring(B, "long long"); + } else { + luaL_error(L, "internal error - bad type"); + } + break; + + default: + luaL_error(L, "internal error - bad type %d", ct->type); + } + + for (i = 0; i < ct->pointers - ct->is_array; i++) { + luaL_addchar(B, '*'); + if (ct->const_mask & (1 << (ct->pointers - i - 1))) { + luaL_addstring(B, " const"); + } + } + } + + if (type == BOTH || type == BACK) { + if (ct->is_reference) { + luaL_addstring(B, "(&)"); + } + + if (ct->is_variable_array && !ct->variable_size_known) { + luaL_addstring(B, "[?]"); + } else if (ct->is_array) { + lua_pushfstring(L, "[%d]", (int)ct->array_size); + luaL_addvalue(B); + } + + if (ct->type == FUNCTION_PTR_TYPE || ct->type == FUNCTION_TYPE) { + lua_pushlightuserdata(L, &g_back_name_key); + lua_rawget(L, usr); + luaL_addvalue(B); + } + + if (ct->is_bitfield) { + lua_pushfstring(L, " : %d", (int)ct->bit_size); + luaL_addvalue(B); + } + } +} + +void push_type_name(lua_State* L, int usr, const struct ctype* ct) { + luaL_Buffer B; + usr = lua_absindex(L, usr); + luaL_buffinit(L, &B); + append_type_name(&B, usr, ct, BOTH); + luaL_pushresult(&B); +} + +static void push_function_type_strings(lua_State* L, int usr, const struct ctype* ct) { + size_t i, args; + luaL_Buffer B; + int top = lua_gettop(L); + const struct ctype* ret_ct; + + int arg_ct = top + 3; + int arg_usr = top + 4; + int ret_usr = top + 6; + + usr = lua_absindex(L, usr); + + /* return type */ + lua_settop(L, top + 4); /* room for two returns and two temp positions */ + lua_rawgeti(L, usr, 0); + lua_getuservalue(L, -1); + ret_ct = (const struct ctype*)lua_touserdata(L, -2); + + luaL_buffinit(L, &B); + append_type_name(&B, ret_usr, ret_ct, FRONT); + + if (ret_ct->type != FUNCTION_TYPE && ret_ct->type != FUNCTION_PTR_TYPE) { + luaL_addchar(&B, ' '); + } + + switch (ct->calling_convention) { + case STD_CALL: + luaL_addstring(&B, "(__stdcall *"); + break; + case FAST_CALL: + luaL_addstring(&B, "(__fastcall *"); + break; + case C_CALL: + luaL_addstring(&B, "(*"); + break; + default: + luaL_error(L, "internal error - unknown calling convention"); + } + + luaL_pushresult(&B); + lua_replace(L, top + 1); + + luaL_buffinit(L, &B); + luaL_addstring(&B, ")("); + + /* arguments */ + args = lua_rawlen(L, usr); + for (i = 1; i <= args; i++) { + if (i > 1) { + luaL_addstring(&B, ", "); + } + + /* note push the arg and user value below the indexes used by the buffer + * and use indexes relative to top to avoid problems due to the buffer + * system pushing a variable number of arguments onto the stack */ + lua_rawgeti(L, usr, (int)i); + lua_replace(L, arg_ct); + lua_getuservalue(L, arg_ct); + lua_replace(L, arg_usr); + append_type_name(&B, arg_usr, (const struct ctype*)lua_touserdata(L, arg_ct), + BOTH); + } + + luaL_addstring(&B, ")"); + append_type_name(&B, ret_usr, ret_ct, BACK); + luaL_pushresult(&B); + lua_replace(L, top + 2); + + lua_settop(L, top + 2); + assert(lua_isstring(L, top + 1) && lua_isstring(L, top + 2)); +} + +/* parses from after the opening paranthesis to after the closing parenthesis */ +static void parse_function_arguments(lua_State* L, struct parser* P, int ct_usr, + struct ctype* ct) { + struct token tok; + int args = 0; + int top = lua_gettop(L); + + ct_usr = lua_absindex(L, ct_usr); + + for (;;) { + require_token(L, P, &tok); + + if (tok.type == TOK_CLOSE_PAREN) { + break; + } + + if (args) { + if (tok.type != TOK_COMMA) { + luaL_error(L, "unexpected token in function argument %d on line %d", args, + P->line); + } + + require_token(L, P, &tok); + } + + if (tok.type == TOK_VA_ARG) { + ct->has_var_arg = true; + check_token(L, P, TOK_CLOSE_PAREN, "", + "unexpected token after ... in function on line %d", P->line); + break; + + } else if (tok.type == TOK_TOKEN) { + struct ctype at; + + put_back(P); + parse_type(L, P, &at); + parse_argument(L, P, -1, &at, NULL, NULL); + + assert(lua_gettop(L) == top + 2); + + /* array arguments are just treated as their base pointer type */ + at.is_array = 0; + + /* check for the c style int func(void) and error on other uses of arguments + * of type void */ + if (at.type == VOID_TYPE && at.pointers == 0) { + if (args) { + luaL_error(L, "can't have argument of type void on line %d", P->line); + } + + check_token(L, P, TOK_CLOSE_PAREN, "", + "unexpected void in function on line %d", P->line); + lua_pop(L, 2); + break; + } + + push_ctype(L, -1, &at); + lua_rawseti(L, ct_usr, ++args); + + lua_pop(L, 2); /* parse_type and parse_argument at_usr */ + + } else { + luaL_error(L, "unexpected token in function argument %d on line %d", args + 1, + P->line); + } + } + + assert(lua_gettop(L) == top); +} + +static int max_bitfield_size(int type) { + switch (type) { + case BOOL_TYPE: + return 1; + case INT8_TYPE: + return 8; + case INT16_TYPE: + return 16; + case INT32_TYPE: + case ENUM_TYPE: + return 32; + case INT64_TYPE: + return 64; + default: + return -1; + } +} + +static struct ctype* parse_argument2(lua_State* L, struct parser* P, int ct_usr, + struct ctype* ct, struct token* name, + struct parser* asmname); + +/* parses from after the first ( in a function declaration or function pointer + * can be one of: + * void foo(...) before ... + * void (foo)(...) before foo + * void (* <>)(...) before <> which is the inner type + */ +static struct ctype* parse_function(lua_State* L, struct parser* P, int ct_usr, + struct ctype* ct, struct token* name, + struct parser* asmname) { + /* We have a function pointer or a function. The usr table will + * get replaced by the canonical one (if there is one) in + * find_canonical_usr after all the arguments and returns have + * been parsed. */ + struct token tok; + int top = lua_gettop(L); + struct ctype* ret; + + lua_newtable(L); + ret = push_ctype(L, ct_usr, ct); + lua_rawseti(L, -2, 0); + ct_usr = lua_gettop(L); + + memset(ct, 0, sizeof(*ct)); + ct->base_size = sizeof(void (*)()); + ct->align_mask = min(FUNCTION_ALIGN_MASK, P->align_mask); + ct->type = FUNCTION_TYPE; + ct->is_defined = 1; + + if (name->type == TOK_NIL) { + for (;;) { + require_token(L, P, &tok); + + if (tok.type == TOK_STAR) { + if (ct->type == FUNCTION_TYPE) { + ct->type = FUNCTION_PTR_TYPE; + } else if (ct->pointers == POINTER_MAX) { + luaL_error(L, + "maximum number of pointer derefs reached - use a struct " + "to break up the pointers on line %d", + P->line); + } else { + ct->pointers++; + ct->const_mask <<= 1; + } + + } else if (parse_attribute(L, P, &tok, ct, asmname)) { + /* parse_attribute sets the appropriate fields */ + + } else { + /* call parse_argument to handle the inner contents + * e.g. the <> in "void (* <>) (...)". Note that the + * inner contents can itself be a function, a function + * ptr, array, etc (e.g. "void (*signal(int sig, void + * (*func)(int)))(int)" ). + */ + put_back(P); + ct = parse_argument2(L, P, ct_usr, ct, name, asmname); + break; + } + } + + check_token(L, P, TOK_CLOSE_PAREN, NULL, + "unexpected token in function on line %d", P->line); + check_token(L, P, TOK_OPEN_PAREN, NULL, "unexpected token in function on line %d", + P->line); + } + + parse_function_arguments(L, P, ct_usr, ct); + + /* if we have an inner function then set the outer function ptr as its + * return type and return the inner function + * e.g. for void (* )(int) inner is + * surrounded by <>, return type is void (*)(int) + */ + if (lua_gettop(L) == ct_usr + 1) { + lua_replace(L, ct_usr); + } + + assert(lua_gettop(L) == top + 1 && lua_istable(L, -1)); + return ret; +} + +static struct ctype* parse_argument2(lua_State* L, struct parser* P, int ct_usr, + struct ctype* ct, struct token* name, + struct parser* asmname) { + struct token tok; + int top = lua_gettop(L); + int ft_usr = 0; + + luaL_checkstack(L, 10, "function too complex"); + ct_usr = lua_absindex(L, ct_usr); + + for (;;) { + if (!next_token(L, P, &tok)) { + /* we've reached the end of the string */ + break; + + } else if (tok.type == TOK_STAR) { + if (ct->pointers == POINTER_MAX) { + luaL_error(L, + "maximum number of pointer derefs reached - use a struct to " + "break up the pointers on line %d", + P->line); + } + + ct->pointers++; + ct->const_mask <<= 1; + + /* __declspec(align(#)) may come before the type in a member */ + if (!ct->is_packed) { + ct->align_mask = max(min(PTR_ALIGN_MASK, P->align_mask), ct->align_mask); + } + + } else if (tok.type == TOK_REFERENCE) { + luaL_error(L, "NYI: c++ reference types"); + + } else if (parse_attribute(L, P, &tok, ct, asmname)) { + /* parse attribute has filled out appropriate fields in type */ + + } else if (tok.type == TOK_OPEN_PAREN) { + ct = parse_function(L, P, ct_usr, ct, name, asmname); + ft_usr = lua_gettop(L); + + } else if (tok.type == TOK_OPEN_SQUARE) { + /* array */ + if (ct->pointers == POINTER_MAX) { + luaL_error(L, + "maximum number of pointer derefs reached - use a struct to " + "break up the pointers"); + } + ct->is_array = 1; + ct->pointers++; + ct->const_mask <<= 1; + require_token(L, P, &tok); + + if (ct->pointers == 1 && !ct->is_defined) { + luaL_error(L, "array of undefined type on line %d", P->line); + } + + if (ct->is_variable_struct || ct->is_variable_array) { + luaL_error(L, "can't have an array of a variably sized type on line %d", + P->line); + } + + if (tok.type == TOK_QUESTION) { + ct->is_variable_array = 1; + ct->variable_increment = + (ct->pointers > 1) ? sizeof(void*) : ct->base_size; + check_token(L, P, TOK_CLOSE_SQUARE, "", + "invalid character in array on line %d", P->line); + + } else if (tok.type == TOK_CLOSE_SQUARE) { + ct->array_size = 0; + + } else if (tok.type == TOK_TOKEN && IS_RESTRICT(tok)) { + /* odd gcc extension foo[__restrict] for arguments */ + ct->array_size = 0; + check_token(L, P, TOK_CLOSE_SQUARE, "", + "invalid character in array on line %d", P->line); + + } else { + int64_t asize; + put_back(P); + asize = calculate_constant(L, P); + if (asize < 0) { + luaL_error(L, "array size can not be negative on line %d", P->line); + } + ct->array_size = (size_t)asize; + check_token(L, P, TOK_CLOSE_SQUARE, "", + "invalid character in array on line %d", P->line); + } + + } else if (tok.type == TOK_COLON) { + int64_t bsize = calculate_constant(L, P); + + if (ct->pointers || bsize < 0 || bsize > max_bitfield_size(ct->type)) { + luaL_error(L, "invalid bitfield on line %d", P->line); + } + + ct->is_bitfield = 1; + ct->bit_size = (unsigned)bsize; + + } else if (tok.type != TOK_TOKEN) { + /* we've reached the end of the declaration */ + put_back(P); + break; + + } else if (IS_CONST(tok)) { + ct->const_mask |= 1; + + } else if (IS_VOLATILE(tok) || IS_RESTRICT(tok)) { + /* ignored for now */ + + } else { + *name = tok; + } + } + + assert((ft_usr == 0 && lua_gettop(L) == top) || + (lua_gettop(L) == top + 1 && ft_usr == top + 1 && + (lua_istable(L, -1) || lua_isnil(L, -1)))); + return ct; +} + +static void find_canonical_usr(lua_State* L, int ct_usr, const struct ctype* ct) { + struct ctype rt; + int top = lua_gettop(L); + int types; + + if (ct->type != FUNCTION_PTR_TYPE && ct->type != FUNCTION_TYPE) { + return; + } + + luaL_checkstack(L, 10, "function too complex"); + ct_usr = lua_absindex(L, ct_usr); + + /* check to see if we already have the canonical usr table */ + lua_pushlightuserdata(L, &g_name_key); + lua_rawget(L, ct_usr); + if (!lua_isnil(L, -1)) { + lua_pop(L, 1); + assert(top == lua_gettop(L)); + return; + } + lua_pop(L, 1); + + assert(top == lua_gettop(L)); + + /* first canonize the return type */ + lua_rawgeti(L, ct_usr, 0); + rt = *(struct ctype*)lua_touserdata(L, -1); + lua_getuservalue(L, -1); + find_canonical_usr(L, -1, &rt); + push_ctype(L, -1, &rt); + lua_rawseti(L, ct_usr, 0); + lua_pop(L, 2); /* return ctype and usr */ + + assert(top == lua_gettop(L)); + + /* look up the type string in the types table */ + push_upval(L, &types_key); + types = lua_gettop(L); + + push_function_type_strings(L, ct_usr, ct); + lua_pushvalue(L, -2); + lua_pushvalue(L, -2); + lua_concat(L, 2); + + lua_pushvalue(L, -1); + lua_rawget(L, types); + + assert(lua_gettop(L) == types + 4 && types == top + 1); + /* stack: types, front, back, both, looked up value */ + + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + + lua_pushlightuserdata(L, &g_front_name_key); + lua_pushvalue(L, -4); + lua_rawset(L, ct_usr); + + lua_pushlightuserdata(L, &g_back_name_key); + lua_pushvalue(L, -3); + lua_rawset(L, ct_usr); + + lua_pushlightuserdata(L, &g_name_key); + lua_pushvalue(L, -2); + lua_rawset(L, ct_usr); + + lua_pushvalue(L, -1); + push_ctype(L, ct_usr, ct); + lua_rawset(L, types); + } else { + lua_getuservalue(L, -1); + lua_replace(L, ct_usr); + lua_pop(L, 1); + } + + lua_pop(L, 4); + assert(top == lua_gettop(L) && types == top + 1); +} + +/* parses after the main base type of a typedef, function argument or + * struct/union member + * eg for const void* bar[3] the base type is void with the subtype so far of + * const, this parses the "* bar[3]" and updates the type argument + * + * ct_usr and type must be as filled out by parse_type + * + * pushes the updated user value on the top of the stack + */ +void parse_argument(lua_State* L, struct parser* P, int ct_usr, struct ctype* ct, + struct token* pname, struct parser* asmname) { + struct token tok, name; + int top = lua_gettop(L); + + memset(&name, 0, sizeof(name)); + parse_argument2(L, P, ct_usr, ct, &name, asmname); + + for (;;) { + if (!next_token(L, P, &tok)) { + break; + } else if (parse_attribute(L, P, &tok, ct, asmname)) { + /* parse_attribute sets the appropriate fields */ + } else { + put_back(P); + break; + } + } + + if (lua_gettop(L) == top) { + lua_pushvalue(L, ct_usr); + } + + find_canonical_usr(L, -1, ct); + + if (pname) { + *pname = name; + } +} + +static void parse_typedef(lua_State* L, struct parser* P) { + struct token tok; + struct ctype base_type; + int top = lua_gettop(L); + + parse_type(L, P, &base_type); + + for (;;) { + struct ctype arg_type = base_type; + struct token name; + + memset(&name, 0, sizeof(name)); + + assert(lua_gettop(L) == top + 1); + parse_argument(L, P, -1, &arg_type, &name, NULL); + assert(lua_gettop(L) == top + 2); + + if (!name.size) { + luaL_error(L, "Can't have a typedef without a name on line %d", P->line); + } else if (arg_type.is_variable_array) { + luaL_error(L, "Can't typedef a variable length array on line %d", P->line); + } + + push_upval(L, &types_key); + lua_pushlstring(L, name.str, name.size); + push_ctype(L, -3, &arg_type); + lua_rawset(L, -3); + lua_pop(L, 2); /* types and parse_argument usr tbl */ + + require_token(L, P, &tok); + + if (tok.type == TOK_SEMICOLON) { + break; + } else if (tok.type != TOK_COMMA) { + luaL_error(L, "Unexpected character in typedef on line %d", P->line); + } + } + + lua_pop(L, 1); /* parse_type usr tbl */ + assert(lua_gettop(L) == top); +} + +static bool is_hex(char ch) { + return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || + ('A' <= ch && ch <= 'F'); +} + +static bool is_digit(char ch) { return '0' <= ch && ch <= '9'; } + +static int from_hex(char ch) { + if (ch >= 'a') { + return ch - 'a' + 10; + } else if (ch >= 'A') { + return ch - 'A' + 10; + } else { + return ch - '0'; + } +} + +static void push_strings(lua_State* L, struct parser* P) { + luaL_Buffer B; + luaL_buffinit(L, &B); + + for (;;) { + const char *p, *e; + char *t, *s; + struct token tok; + + require_token(L, P, &tok); + if (tok.type != TOK_STRING) { + break; + } + + p = tok.str; + e = p + tok.size; + + t = luaL_prepbuffsize(&B, tok.size); + s = t; + + while (p < e) { + if (*p == '\\') { + if (++p == e) { + luaL_error(L, "parse error in string"); + } + switch (*p) { + case '\\': + *(t++) = '\\'; + p++; + break; + case '\"': + *(t++) = '\"'; + p++; + break; + case '\'': + *(t++) = '\''; + p++; + break; + case 'n': + *(t++) = '\n'; + p++; + break; + case 'r': + *(t++) = '\r'; + p++; + break; + case 'b': + *(t++) = '\b'; + p++; + break; + case 't': + *(t++) = '\t'; + p++; + break; + case 'f': + *(t++) = '\f'; + p++; + break; + case 'a': + *(t++) = '\a'; + p++; + break; + case 'v': + *(t++) = '\v'; + p++; + break; + case 'e': + *(t++) = 0x1B; + p++; + break; + case 'x': { + uint8_t u; + p++; + if (p + 2 > e || !is_hex(p[0]) || !is_hex(p[1])) { + luaL_error(L, "parse error in string"); + } + u = (from_hex(p[0]) << 4) | from_hex(p[1]); + *(t++) = *(char*)&u; + p += 2; + break; + } + default: { + uint8_t u; + const char* e2 = min(p + 3, e); + if (!is_digit(*p)) { + luaL_error(L, "parse error in string"); + } + u = *p - '0'; + p++; + while (is_digit(*p) && p < e2) { + u = 10 * u + *p - '0'; + p++; + } + *(t++) = *(char*)&u; + break; + } + } + } else { + *(t++) = *(p++); + } + } + + luaL_addsize(&B, t - s); + } + + luaL_pushresult(&B); +} + +#define END 0 +#define PRAGMA_POP 1 + +static int parse_root(lua_State* L, struct parser* P) { + int top = lua_gettop(L); + struct token tok; + + while (next_token(L, P, &tok)) { + /* we can have: + * struct definition + * enum definition + * union definition + * struct/enum/union declaration + * typedef + * function declaration + * pragma pack + */ + + assert(lua_gettop(L) == top); + + if (tok.type == TOK_SEMICOLON) { + /* empty semicolon in root continue on */ + + } else if (tok.type == TOK_POUND) { + check_token(L, P, TOK_TOKEN, "pragma", + "unexpected pre processor directive on line %d", P->line); + check_token(L, P, TOK_TOKEN, "pack", + "unexpected pre processor directive on line %d", P->line); + check_token(L, P, TOK_OPEN_PAREN, "", "invalid pack directive on line %d", + P->line); + + require_token(L, P, &tok); + + if (tok.type == TOK_NUMBER) { + if (tok.integer != 1 && tok.integer != 2 && tok.integer != 4 && + tok.integer != 8 && tok.integer != 16) { + luaL_error(L, "pack directive with invalid pack size on line %d", + P->line); + } + + P->align_mask = (unsigned)(tok.integer - 1); + check_token(L, P, TOK_CLOSE_PAREN, "", + "invalid pack directive on line %d", P->line); + + } else if (tok.type == TOK_TOKEN && IS_LITERAL(tok, "push")) { + int line = P->line; + unsigned previous_alignment = P->align_mask; + + check_token(L, P, TOK_CLOSE_PAREN, "", + "invalid pack directive on line %d", P->line); + + if (parse_root(L, P) != PRAGMA_POP) { + luaL_error(L, + "reached end of string without a pragma pop to match the " + "push on line %d", + line); + } + + P->align_mask = previous_alignment; + + } else if (tok.type == TOK_TOKEN && IS_LITERAL(tok, "pop")) { + check_token(L, P, TOK_CLOSE_PAREN, "", + "invalid pack directive on line %d", P->line); + return PRAGMA_POP; + + } else { + luaL_error(L, "invalid pack directive on line %d", P->line); + } + + } else if (tok.type != TOK_TOKEN) { + return luaL_error(L, "unexpected character on line %d", P->line); + + } else if (IS_LITERAL(tok, "__extension__")) { + /* ignore */ + continue; + + } else if (IS_LITERAL(tok, "extern")) { + /* ignore extern as data and functions can only be extern */ + continue; + + } else if (IS_LITERAL(tok, "typedef")) { + parse_typedef(L, P); + + } else if (IS_LITERAL(tok, "static")) { + struct ctype at; + + int64_t val; + require_token(L, P, &tok); + if (!IS_CONST(tok)) { + luaL_error(L, "expected 'static const int' on line %d", P->line); + } + + parse_type(L, P, &at); + + require_token(L, P, &tok); + if (tok.type != TOK_TOKEN) { + luaL_error(L, + "expected constant name after 'static const int' on line %d", + P->line); + } + + check_token(L, P, TOK_ASSIGN, "", + "expected = after 'static const int ' on line %d", P->line); + + val = calculate_constant(L, P); + + check_token(L, P, TOK_SEMICOLON, "", + "expected ; after 'static const int' definition on line %d", + P->line); + + push_upval(L, &constants_key); + lua_pushlstring(L, tok.str, tok.size); + + switch (at.type) { + case INT8_TYPE: + case INT16_TYPE: + case INT32_TYPE: + if (at.is_unsigned) + lua_pushnumber(L, (unsigned int)val); + else + lua_pushnumber(L, (int)val); + break; + + default: + luaL_error(L, + "expected a valid 8-, 16-, or 32-bit signed or unsigned " + "integer type after 'static const' on line %d", + P->line); + } + + lua_rawset(L, -3); + lua_pop(L, 2); /*constants and type*/ + + } else { + /* type declaration, type definition, or function declaration */ + struct ctype type; + struct token name; + struct parser asmname; + + memset(&name, 0, sizeof(name)); + memset(&asmname, 0, sizeof(asmname)); + + put_back(P); + parse_type(L, P, &type); + + for (;;) { + parse_argument(L, P, -1, &type, &name, &asmname); + + if (name.size) { + /* global/function declaration */ + + /* set asmname_tbl[name] = asmname */ + if (asmname.next) { + push_upval(L, &asmname_key); + lua_pushlstring(L, name.str, name.size); + push_strings(L, &asmname); + lua_rawset(L, -3); + lua_pop(L, 1); /* asmname upval */ + } + + push_upval(L, &functions_key); + lua_pushlstring(L, name.str, name.size); + push_ctype(L, -3, &type); + lua_rawset(L, -3); + lua_pop(L, 1); /* functions upval */ + } else { + /* type declaration/definition - already been processed */ + } + + lua_pop(L, 1); + + require_token(L, P, &tok); + + if (tok.type == TOK_SEMICOLON) { + break; + } else if (tok.type != TOK_COMMA) { + luaL_error(L, "missing semicolon on line %d", P->line); + } + } + + lua_pop(L, 1); + } + } + + return END; +} + +int ffi_cdef(lua_State* L) { + struct parser P; + + P.line = 1; + P.prev = P.next = luaL_checkstring(L, 1); + P.align_mask = DEFAULT_ALIGN_MASK; + + if (parse_root(L, &P) == PRAGMA_POP) { + luaL_error(L, "pragma pop without an associated push on line %d", P.line); + } + + return 0; +} + +/* calculate_constant handles operator precedence by having a number of + * recursive commands each of which computes the result at that level of + * precedence and above. calculate_constant1 is the highest precedence + */ + +static int try_cast(lua_State* L) { + struct parser* P = (struct parser*)lua_touserdata(L, 1); + struct ctype ct; + struct token name, tok; + memset(&name, 0, sizeof(name)); + + parse_type(L, P, &ct); + parse_argument(L, P, -1, &ct, &name, NULL); + + require_token(L, P, &tok); + if (tok.type != TOK_CLOSE_PAREN || name.size) { + return luaL_error(L, "invalid cast"); + } + + if (ct.pointers || ct.type != INT32_TYPE) { + return luaL_error(L, "unsupported cast on line %d", P->line); + } + + return 0; +} + +static int64_t calculate_constant2(lua_State* L, struct parser* P, struct token* tok); + +/* () */ +static int64_t calculate_constant1(lua_State* L, struct parser* P, struct token* tok) { + int64_t ret; + + if (tok->type == TOK_NUMBER) { + ret = tok->integer; + next_token(L, P, tok); + return ret; + + } else if (tok->type == TOK_TOKEN) { + /* look up name in constants table */ + push_upval(L, &constants_key); + lua_pushlstring(L, tok->str, tok->size); + lua_rawget(L, -2); + lua_remove(L, -2); /* constants table */ + + if (!lua_isnumber(L, -1)) { + lua_pushlstring(L, tok->str, tok->size); + luaL_error(L, "use of undefined constant %s on line %d", lua_tostring(L, -1), + P->line); + } + + ret = (int64_t)lua_tonumber(L, -1); + lua_pop(L, 1); + next_token(L, P, tok); + return ret; + + } else if (tok->type == TOK_OPEN_PAREN) { + struct parser before_cast = *P; + int top = lua_gettop(L); + + /* see if this is a numeric cast, which we ignore */ + lua_pushcfunction(L, &try_cast); + lua_pushlightuserdata(L, P); + if (!lua_pcall(L, 1, 0, 0)) { + next_token(L, P, tok); + return calculate_constant2(L, P, tok); + } + lua_settop(L, top); + + *P = before_cast; + ret = calculate_constant(L, P); + + require_token(L, P, tok); + if (tok->type != TOK_CLOSE_PAREN) { + luaL_error(L, "error whilst parsing constant at line %d", P->line); + } + + next_token(L, P, tok); + return ret; + + } else { + return luaL_error(L, "unexpected token whilst parsing constant at line %d", + P->line); + } +} + +/* ! and ~, unary + and -, and sizeof */ +static int64_t calculate_constant2(lua_State* L, struct parser* P, struct token* tok) { + if (tok->type == TOK_LOGICAL_NOT) { + require_token(L, P, tok); + return !calculate_constant2(L, P, tok); + + } else if (tok->type == TOK_BITWISE_NOT) { + require_token(L, P, tok); + return ~calculate_constant2(L, P, tok); + + } else if (tok->type == TOK_PLUS) { + require_token(L, P, tok); + return calculate_constant2(L, P, tok); + + } else if (tok->type == TOK_MINUS) { + require_token(L, P, tok); + return -calculate_constant2(L, P, tok); + + } else if (tok->type == TOK_TOKEN && + (IS_LITERAL(*tok, "sizeof") || IS_LITERAL(*tok, "alignof") || + IS_LITERAL(*tok, "__alignof__") || IS_LITERAL(*tok, "__alignof"))) { + bool issize = IS_LITERAL(*tok, "sizeof"); + struct ctype type; + + require_token(L, P, tok); + if (tok->type != TOK_OPEN_PAREN) { + luaL_error(L, "invalid sizeof at line %d", P->line); + } + + parse_type(L, P, &type); + parse_argument(L, P, -1, &type, NULL, NULL); + lua_pop(L, 2); + + require_token(L, P, tok); + if (tok->type != TOK_CLOSE_PAREN) { + luaL_error(L, "invalid sizeof at line %d", P->line); + } + + next_token(L, P, tok); + + return issize ? ctype_size(L, &type) : type.align_mask + 1; + + } else { + return calculate_constant1(L, P, tok); + } +} + +/* binary * / and % (left associative) */ +static int64_t calculate_constant3(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant2(L, P, tok); + + for (;;) { + if (tok->type == TOK_MULTIPLY) { + require_token(L, P, tok); + left *= calculate_constant2(L, P, tok); + + } else if (tok->type == TOK_DIVIDE) { + require_token(L, P, tok); + left /= calculate_constant2(L, P, tok); + + } else if (tok->type == TOK_MODULUS) { + require_token(L, P, tok); + left %= calculate_constant2(L, P, tok); + + } else { + return left; + } + } +} + +/* binary + and - (left associative) */ +static int64_t calculate_constant4(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant3(L, P, tok); + + for (;;) { + if (tok->type == TOK_PLUS) { + require_token(L, P, tok); + left += calculate_constant3(L, P, tok); + + } else if (tok->type == TOK_MINUS) { + require_token(L, P, tok); + left -= calculate_constant3(L, P, tok); + + } else { + return left; + } + } +} + +/* binary << and >> (left associative) */ +static int64_t calculate_constant5(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant4(L, P, tok); + + for (;;) { + if (tok->type == TOK_LEFT_SHIFT) { + require_token(L, P, tok); + left <<= calculate_constant4(L, P, tok); + + } else if (tok->type == TOK_RIGHT_SHIFT) { + require_token(L, P, tok); + left >>= calculate_constant4(L, P, tok); + + } else { + return left; + } + } +} + +/* binary <, <=, >, and >= (left associative) */ +static int64_t calculate_constant6(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant5(L, P, tok); + + for (;;) { + if (tok->type == TOK_LESS) { + require_token(L, P, tok); + left = (left < calculate_constant5(L, P, tok)); + + } else if (tok->type == TOK_LESS_EQUAL) { + require_token(L, P, tok); + left = (left <= calculate_constant5(L, P, tok)); + + } else if (tok->type == TOK_GREATER) { + require_token(L, P, tok); + left = (left > calculate_constant5(L, P, tok)); + + } else if (tok->type == TOK_GREATER_EQUAL) { + require_token(L, P, tok); + left = (left >= calculate_constant5(L, P, tok)); + + } else { + return left; + } + } +} + +/* binary ==, != (left associative) */ +static int64_t calculate_constant7(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant6(L, P, tok); + + for (;;) { + if (tok->type == TOK_EQUAL) { + require_token(L, P, tok); + left = (left == calculate_constant6(L, P, tok)); + + } else if (tok->type == TOK_NOT_EQUAL) { + require_token(L, P, tok); + left = (left != calculate_constant6(L, P, tok)); + + } else { + return left; + } + } +} + +/* binary & (left associative) */ +static int64_t calculate_constant8(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant7(L, P, tok); + + for (;;) { + if (tok->type == TOK_BITWISE_AND) { + require_token(L, P, tok); + left = (left & calculate_constant7(L, P, tok)); + + } else { + return left; + } + } +} + +/* binary ^ (left associative) */ +static int64_t calculate_constant9(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant8(L, P, tok); + + for (;;) { + if (tok->type == TOK_BITWISE_XOR) { + require_token(L, P, tok); + left = (left ^ calculate_constant8(L, P, tok)); + + } else { + return left; + } + } +} + +/* binary | (left associative) */ +static int64_t calculate_constant10(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant9(L, P, tok); + + for (;;) { + if (tok->type == TOK_BITWISE_OR) { + require_token(L, P, tok); + left = (left | calculate_constant9(L, P, tok)); + + } else { + return left; + } + } +} + +/* binary && (left associative) */ +static int64_t calculate_constant11(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant10(L, P, tok); + + for (;;) { + if (tok->type == TOK_LOGICAL_AND) { + require_token(L, P, tok); + left = (left && calculate_constant10(L, P, tok)); + + } else { + return left; + } + } +} + +/* binary || (left associative) */ +static int64_t calculate_constant12(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant11(L, P, tok); + + for (;;) { + if (tok->type == TOK_LOGICAL_OR) { + require_token(L, P, tok); + left = (left || calculate_constant11(L, P, tok)); + + } else { + return left; + } + } +} + +/* ternary ?: (right associative) */ +static int64_t calculate_constant13(lua_State* L, struct parser* P, struct token* tok) { + int64_t left = calculate_constant12(L, P, tok); + + if (tok->type == TOK_QUESTION) { + int64_t middle, right; + require_token(L, P, tok); + middle = calculate_constant13(L, P, tok); + if (tok->type != TOK_COLON) { + luaL_error(L, "invalid ternery (? :) in constant on line %d", P->line); + } + require_token(L, P, tok); + right = calculate_constant13(L, P, tok); + return left ? middle : right; + + } else { + return left; + } +} + +int64_t calculate_constant(lua_State* L, struct parser* P) { + struct token tok; + int64_t ret; + require_token(L, P, &tok); + ret = calculate_constant13(L, P, &tok); + + if (tok.type != TOK_NIL) { + put_back(P); + } + + return ret; +} diff --git a/src/tinternalizedfiles.cpp b/src/tinternalizedfiles.cpp index 198270da..cd7e0421 100644 --- a/src/tinternalizedfiles.cpp +++ b/src/tinternalizedfiles.cpp @@ -4,9 +4,13 @@ #include "terra.h" void terra_registerinternalizedfiles(lua_State* L, int terratable) { - lua_getfield(L, terratable, "registerinternalizedfiles"); - lua_pushlightuserdata(L, &headerfile_names[0]); - lua_pushlightuserdata(L, &headerfile_contents[0]); - lua_pushlightuserdata(L, &headerfile_sizes[0]); - lua_call(L, 3, 0); -} \ No newline at end of file + lua_getfield(L, terratable, "registerinternalizedfile"); + for (int i = 0; headerfile_names[i] != NULL; i++) { + lua_pushvalue(L, -1); + lua_pushstring(L, headerfile_names[i]); + lua_pushlightuserdata(L, (void*)headerfile_contents[i]); + lua_pushnumber(L, headerfile_sizes[i]); + lua_call(L, 3, 0); + } + lua_pop(L, 1); +} diff --git a/src/tobj.h b/src/tobj.h index 942917fe..ed606a12 100644 --- a/src/tobj.h +++ b/src/tobj.h @@ -8,25 +8,13 @@ extern "C" { } #include "tkind.h" -// helper function to handle cdata objects passed to C using legacy API -static inline void *terra_tocdatapointer(lua_State *L, int idx) { - if (10 != lua_type(L, idx)) - return NULL; // not a cdata, 10 is from LuaJIT sources since it is not exposed in - // the normal API - // argument is a 'cdata'. - // calling topointer on it will return a pointer to the cdata payload - void *const *cdata = (void *const *)lua_topointer(L, idx); - if (!cdata) return NULL; - return *cdata; -} - // object to hold reference to lua object and help extract information struct Obj { Obj() { ref = LUA_NOREF; L = NULL; } - void initFromStack(lua_State *L, int ref_table) { + void initFromStack(lua_State* L, int ref_table) { freeref(); this->L = L; this->ref_table = ref_table; @@ -40,7 +28,7 @@ struct Obj { pop(); return i; } - bool objAt(int i, Obj *r) { + bool objAt(int i, Obj* r) { push(); lua_rawgeti(L, -1, i + 1); // stick to 0-based indexing in C code... if (lua_isnil(L, -1)) { @@ -51,45 +39,45 @@ struct Obj { pop(); return true; } - double number(const char *field) { + double number(const char* field) { push(); lua_getfield(L, -1, field); double r = lua_tonumber(L, -1); pop(2); return r; } - uint64_t integer(const char *field) { + uint64_t integer(const char* field) { push(); lua_getfield(L, -1, field); - const void *ud = lua_touserdata(L, -1); + const void* ud = lua_touserdata(L, -1); pop(2); - uint64_t i = *(const uint64_t *)ud; + uint64_t i = *(const uint64_t*)ud; return i; } - bool boolean(const char *field) { + bool boolean(const char* field) { push(); lua_getfield(L, -1, field); bool v = lua_toboolean(L, -1); pop(2); return v; } - const char *string(const char *field) { + const char* string(const char* field) { push(); lua_getfield(L, -1, field); - const char *r = luaL_checkstring(L, -1); + const char* r = luaL_checkstring(L, -1); pop(2); return r; } - const char *asstring(const char *field) { + const char* asstring(const char* field) { push(); lua_getfield(L, LUA_GLOBALSINDEX, "tostring"); lua_getfield(L, -2, field); lua_call(L, 1, 1); - const char *r = luaL_checkstring(L, -1); + const char* r = luaL_checkstring(L, -1); pop(2); return r; } - bool obj(const char *field, Obj *r) { + bool obj(const char* field, Obj* r) { push(); lua_getfield(L, -1, field); if (lua_isnil(L, -1)) { @@ -101,26 +89,19 @@ struct Obj { return true; } } - void *ud(const char *field) { - push(); - lua_getfield(L, -1, field); - void *u = lua_touserdata(L, -1); - pop(2); - return u; - } - void *cd(const char *field) { + void* ud(const char* field) { push(); lua_getfield(L, -1, field); - void *u = terra_tocdatapointer(L, -1); + void* u = lua_touserdata(L, -1); pop(2); return u; } - void pushfield(const char *field) { + void pushfield(const char* field) { push(); lua_getfield(L, -1, field); lua_remove(L, -2); } - bool hasfield(const char *field) { + bool hasfield(const char* field) { push(); lua_getfield(L, -1, field); bool isNil = lua_isnil(L, -1); @@ -132,7 +113,7 @@ struct Obj { assert(lua_gettop(L) >= ref_table); lua_rawgeti(L, ref_table, ref); } - T_Kind kind(const char *field) { + T_Kind kind(const char* field) { push(); lua_getfield(L, LUA_GLOBALSINDEX, "terra"); lua_getfield(L, -1, "kinds"); @@ -143,14 +124,14 @@ struct Obj { return (T_Kind)k; } void setfield( - const char *key) { // sets field to value on top of the stack and pops it off + const char* key) { // sets field to value on top of the stack and pops it off assert(!lua_isnil(L, -1)); push(); lua_pushvalue(L, -2); lua_setfield(L, -2, key); pop(2); } - void clearfield(const char *key) { + void clearfield(const char* key) { push(); lua_pushnil(L); lua_setfield(L, -2, key); @@ -163,18 +144,18 @@ struct Obj { lua_rawseti(L, -2, s + 1); pop(2); } - void setud(Obj *k, void *ud) { + void setud(Obj* k, void* ud) { push(); k->push(); lua_pushlightuserdata(L, ud); lua_settable(L, -3); pop(1); } - void *getud(Obj *k) { + void* getud(Obj* k) { push(); k->push(); lua_gettable(L, -2); - void *r = lua_touserdata(L, -1); + void* r = lua_touserdata(L, -1); pop(2); return r; } @@ -191,15 +172,15 @@ struct Obj { push(); lua_call(L, 1, 0); } - void newlist(Obj *lst) { + void newlist(Obj* lst) { lua_getfield(L, LUA_GLOBALSINDEX, "terra"); lua_getfield(L, -1, "newlist"); lua_remove(L, -2); lua_call(L, 0, 1); lst->initFromStack(L, ref_table); } - void fromStack(Obj *o) { o->initFromStack(L, ref_table); } - lua_State *getState() { return L; } + void fromStack(Obj* o) { o->initFromStack(L, ref_table); } + lua_State* getState() { return L; } int getRefTable() { return ref_table; } private: @@ -213,15 +194,15 @@ struct Obj { void pop(int n = 1) { lua_pop(L, n); } int ref; int ref_table; - lua_State *L; + lua_State* L; }; -static inline int lobj_newreftable(lua_State *L) { +static inline int lobj_newreftable(lua_State* L) { lua_newtable(L); return lua_gettop(L); } -static inline void lobj_removereftable(lua_State *L, int ref_table) { +static inline void lobj_removereftable(lua_State* L, int ref_table) { assert(lua_gettop(L) == ref_table); lua_pop(L, 1); // remove the reference table from stack }