diff --git a/lib/cext/ABI_version.txt b/lib/cext/ABI_version.txt index 00750edc07d6..b8626c4cff28 100644 --- a/lib/cext/ABI_version.txt +++ b/lib/cext/ABI_version.txt @@ -1 +1 @@ -3 +4 diff --git a/lib/cext/include/ruby/ruby.h b/lib/cext/include/ruby/ruby.h index 549bbed30c24..545110bf5e7b 100644 --- a/lib/cext/include/ruby/ruby.h +++ b/lib/cext/include/ruby/ruby.h @@ -24,7 +24,6 @@ extern "C" { // Must be first, as it defines feature test macros like _GNU_SOURCE, // which influences the definitions exposed by system header files. #include "ruby/config.h" -#include #ifdef RUBY_EXTCONF_H #include RUBY_EXTCONF_H #endif @@ -32,6 +31,8 @@ extern "C" { #include "defines.h" #include "ruby/assert.h" +#include + /* For MinGW, we need __declspec(dllimport) for RUBY_EXTERN on MJIT. mswin's RUBY_EXTERN already has that. See also: win32/Makefile.sub */ #if defined(MJIT_HEADER) && defined(_WIN32) && defined(__GNUC__) diff --git a/lib/cext/include/truffleruby/truffleruby-pre.h b/lib/cext/include/truffleruby/truffleruby-pre.h index d1d30b236670..e0225a847197 100644 --- a/lib/cext/include/truffleruby/truffleruby-pre.h +++ b/lib/cext/include/truffleruby/truffleruby-pre.h @@ -53,6 +53,12 @@ POLYGLOT_DECLARE_TYPE(VALUE) extern void* rb_tr_cext; #define RUBY_CEXT rb_tr_cext +void* rb_tr_abi_version(void) __attribute__((weak)); +void* rb_tr_abi_version(void) { + char* abi_version = STRINGIZE(TRUFFLERUBY_ABI_VERSION); + return polyglot_from_string(abi_version, "US-ASCII"); +} + // Wrapping and unwrapping of values. extern void* (*rb_tr_unwrap)(VALUE obj); diff --git a/lib/truffle/rbconfig.rb b/lib/truffle/rbconfig.rb index 9f99bb47714d..a9b92359610b 100644 --- a/lib/truffle/rbconfig.rb +++ b/lib/truffle/rbconfig.rb @@ -73,7 +73,7 @@ module RbConfig # Determine the various flags for native compilation optflags = '' - debugflags = '' + debugflags = "-DTRUFFLERUBY_ABI_VERSION=#{ruby_abi_version}" warnflags = [ '-Wimplicit-function-declaration', # To make missing C ext functions clear '-Wno-int-conversion', # MRI has VALUE defined as long while we have it as void* diff --git a/lib/truffle/truffle/cext.rb b/lib/truffle/truffle/cext.rb index c9047f16fb51..69f3842f160e 100644 --- a/lib/truffle/truffle/cext.rb +++ b/lib/truffle/truffle/cext.rb @@ -118,6 +118,16 @@ def supported? Interop.mime_type_supported?('application/x-sulong-library') end + def check_abi_version(embedded_abi_version, extension_path) + runtime_abi_version = Truffle::GemUtil.abi_version + if embedded_abi_version != runtime_abi_version + message = "The native extension at #{extension_path} has a different ABI version: #{embedded_abi_version.inspect} " \ + "than the running TruffleRuby: #{runtime_abi_version.inspect}" + warn message, uplevel: 1 + raise LoadError, message + end + end + def rb_stdin $stdin end diff --git a/src/main/c/Makefile b/src/main/c/Makefile index f426ddf6087b..32115b7c1b48 100644 --- a/src/main/c/Makefile +++ b/src/main/c/Makefile @@ -21,10 +21,11 @@ TRUFFLE_POSIX := truffleposix/libtruffleposix.$(SOEXT) SPAWN_HELPER := spawn-helper/spawn-helper RUBY_HEADERS := $(wildcard $(ROOT)/lib/cext/include/*.h) $(wildcard $(ROOT)/lib/cext/include/*/*.h) $(wildcard $(ROOT)/lib/cext/include/*/*/*.h) +ABI_VERSION := $(ROOT)/lib/cext/ABI_version.txt RBCONFIG := $(ROOT)/lib/truffle/rbconfig.rb MKMF := $(ROOT)/lib/mri/mkmf.rb LIBTRUFFLERUBY = cext/libtruffleruby.$(SOEXT) -BASIC_EXTCONF_DEPS := $(SPAWN_HELPER) $(TRUFFLE_POSIX) $(RUBY_HEADERS) $(RBCONFIG) $(MKMF) +BASIC_EXTCONF_DEPS := $(SPAWN_HELPER) $(TRUFFLE_POSIX) $(RUBY_HEADERS) $(ABI_VERSION) $(RBCONFIG) $(MKMF) # C extensions link against libtruffleruby (and might do have_func() checks against it), so it needs to be there before. # However, if libtruffleruby is recompiled, there is no need to rebuild C extensions, so it's a order-only-prerequisite. EXTCONF_DEPS := $(BASIC_EXTCONF_DEPS) | $(LIBTRUFFLERUBY) diff --git a/src/main/java/org/truffleruby/core/CoreLibrary.java b/src/main/java/org/truffleruby/core/CoreLibrary.java index 6702d670e25f..ea399956ad5b 100644 --- a/src/main/java/org/truffleruby/core/CoreLibrary.java +++ b/src/main/java/org/truffleruby/core/CoreLibrary.java @@ -184,6 +184,7 @@ public class CoreLibrary { public final RubyClass truffleFFINullPointerErrorClass; public final RubyModule truffleTypeModule; public final RubyModule truffleModule; + public final RubyModule truffleCExtModule; public final RubyModule truffleInternalModule; public final RubyModule truffleBootModule; public final RubyModule truffleExceptionOperationsModule; @@ -489,7 +490,7 @@ public CoreLibrary(RubyContext context, RubyLanguage language) { truffleInteropModule, interopExceptionClass, "UnknownKeyException"); - defineModule(truffleModule, "CExt"); + truffleCExtModule = defineModule(truffleModule, "CExt"); defineModule(truffleModule, "Debug"); defineModule(truffleModule, "ObjSpace"); defineModule(truffleModule, "Coverage"); diff --git a/src/main/java/org/truffleruby/language/loader/FeatureLoader.java b/src/main/java/org/truffleruby/language/loader/FeatureLoader.java index 13243efae714..645508f1338b 100644 --- a/src/main/java/org/truffleruby/language/loader/FeatureLoader.java +++ b/src/main/java/org/truffleruby/language/loader/FeatureLoader.java @@ -20,12 +20,15 @@ import java.util.Map; import java.util.concurrent.locks.ReentrantLock; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; import org.jcodings.Encoding; import org.jcodings.specific.UTF8Encoding; import org.truffleruby.RubyContext; import org.truffleruby.RubyLanguage; import org.truffleruby.collections.ConcurrentOperations; import org.truffleruby.core.array.ArrayOperations; +import org.truffleruby.core.array.ArrayUtils; import org.truffleruby.core.array.RubyArray; import org.truffleruby.core.encoding.EncodingManager; import org.truffleruby.core.module.RubyModule; @@ -35,7 +38,9 @@ import org.truffleruby.core.thread.RubyThread; import org.truffleruby.extra.TruffleRubyNodes; import org.truffleruby.extra.ffi.Pointer; +import org.truffleruby.interop.InteropNodes; import org.truffleruby.interop.TranslateInteropExceptionNode; +import org.truffleruby.language.Nil; import org.truffleruby.language.RubyConstant; import org.truffleruby.language.control.RaiseException; import org.truffleruby.language.dispatch.DispatchNode; @@ -434,8 +439,7 @@ public void ensureCExtImplementationLoaded(String feature, RequireNode requireNo final String rubyLibPath = context.getRubyHome() + "/lib/cext/libtruffleruby" + Platform.LIB_SUFFIX; final Object library = loadCExtLibRuby(rubyLibPath, feature, requireNode); - final Object initFunction = requireNode - .findFunctionInLibrary(library, "rb_tr_init", rubyLibPath); + final Object initFunction = findFunctionInLibrary(library, "rb_tr_init", rubyLibPath); final InteropLibrary interop = InteropLibrary.getFactory().getUncached(); try { @@ -479,12 +483,63 @@ public Object loadCExtLibrary(String feature, String path, Node currentNode) { final TruffleFile truffleFile = FileLoader.getSafeTruffleFile(context, path); FileLoader.ensureReadable(context, truffleFile, currentNode); - final Source source = Source.newBuilder("llvm", truffleFile).build(); - return context.getEnv().parseInternal(source).call(); - } catch (IOException e) { - throw new RaiseException(context, context.getCoreExceptions().loadError(e, path, currentNode)); + final Source source; + try { + source = Source.newBuilder("llvm", truffleFile).build(); + } catch (IOException e) { + throw new RaiseException(context, context.getCoreExceptions().loadError(e, path, currentNode)); + } + + final Object library = context.getEnv().parseInternal(source).call(); + + final Object embeddedABIVersion = getEmbeddedABIVersion(path, library); + RubyContext.send(context.getCoreLibrary().truffleCExtModule, "check_abi_version", embeddedABIVersion, path); + + return library; } finally { Metrics.printTime("after-load-cext-" + feature); } } + + private Object getEmbeddedABIVersion(String expandedPath, Object library) { + if (!InteropLibrary.getFactory().getUncached(library).isMemberReadable(library, "rb_tr_abi_version")) { + return Nil.INSTANCE; + } + + final Object abiVersionFunction = findFunctionInLibrary(library, "rb_tr_abi_version", expandedPath); + final InteropLibrary abiFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(abiVersionFunction); + final String abiVersion = (String) InteropNodes.execute( + abiVersionFunction, + ArrayUtils.EMPTY_ARRAY, + abiFunctionInteropLibrary, + TranslateInteropExceptionNode.getUncached()); + return StringOperations.createString( + context, + language, + StringOperations.encodeRope(abiVersion, UTF8Encoding.INSTANCE)); + } + + Object findFunctionInLibrary(Object library, String functionName, String path) { + final Object function; + try { + function = InteropLibrary.getFactory().getUncached(library).readMember(library, functionName); + } catch (UnknownIdentifierException e) { + throw new RaiseException( + context, + context.getCoreExceptions().loadError(String.format("%s() not found", functionName), path, null)); + } catch (UnsupportedMessageException e) { + throw TranslateInteropExceptionNode.getUncached().execute(e); + } + + if (function == null) { + throw new RaiseException( + context, + context.getCoreExceptions().loadError( + String.format("%s() not found (READ returned null)", functionName), + path, + null)); + } + + return function; + } } diff --git a/src/main/java/org/truffleruby/language/loader/RequireNode.java b/src/main/java/org/truffleruby/language/loader/RequireNode.java index c48ef9e7770e..15cd7dc4e4fd 100644 --- a/src/main/java/org/truffleruby/language/loader/RequireNode.java +++ b/src/main/java/org/truffleruby/language/loader/RequireNode.java @@ -44,8 +44,6 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.UnknownIdentifierException; -import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.nodes.IndirectCallNode; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; @@ -251,7 +249,6 @@ private void requireCExtension(String feature, String expandedPath, Node current final FeatureLoader featureLoader = getContext().getFeatureLoader(); final Object library; - try { featureLoader.ensureCExtImplementationLoaded(feature, this); @@ -267,10 +264,9 @@ private void requireCExtension(String feature, String expandedPath, Node current } final String initFunctionName = "Init_" + getBaseName(expandedPath); + final Object initFunction = featureLoader.findFunctionInLibrary(library, initFunctionName, expandedPath); - final Object initFunction = findFunctionInLibrary(library, initFunctionName, expandedPath); - - InteropLibrary initFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(initFunction); + final InteropLibrary initFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(initFunction); if (!initFunctionInteropLibrary.isExecutable(initFunction)) { throw new RaiseException( getContext(), @@ -290,28 +286,6 @@ private void requireCExtension(String feature, String expandedPath, Node current } } - Object findFunctionInLibrary(Object library, String functionName, String path) { - final Object function; - try { - function = InteropLibrary.getFactory().getUncached(library).readMember(library, functionName); - } catch (UnknownIdentifierException e) { - throw new RaiseException( - getContext(), - coreExceptions().loadError(String.format("%s() not found", functionName), path, null)); - } catch (UnsupportedMessageException e) { - throw TranslateInteropExceptionNode.getUncached().execute(e); - } - - if (function == null) { - throw new RaiseException( - getContext(), - coreExceptions() - .loadError(String.format("%s() not found (READ returned null)", functionName), path, null)); - } - - return function; - } - @TruffleBoundary private void handleCExtensionException(String feature, Exception e) { TranslateExceptionNode.logJavaException(getContext(), this, e); diff --git a/src/main/ruby/truffleruby/core/truffle/gem_util.rb b/src/main/ruby/truffleruby/core/truffle/gem_util.rb index 18d8de12b575..7076477e41d8 100644 --- a/src/main/ruby/truffleruby/core/truffle/gem_util.rb +++ b/src/main/ruby/truffleruby/core/truffle/gem_util.rb @@ -142,6 +142,15 @@ def self.abi_version @abi_version ||= "#{RUBY_VERSION}.#{Truffle::Boot.basic_abi_version}".freeze end + def self.check_abi_version(embedded_abi_version, extension_path) + if embedded_abi_version != abi_version + message = "The native extension at #{extension_path} has a different ABI version: #{embedded_abi_version.inspect} " \ + "than the running TruffleRuby: #{abi_version.inspect}" + warn message, uplevel: 1 + raise LoadError, message + end + end + def self.expand(path) if File.directory?(path) File.realpath(path) diff --git a/test/truffle/cexts/minimum/ext/minimum/minimum.c b/test/truffle/cexts/minimum/ext/minimum/minimum.c index 83a42ffeb5b7..ab561cefdd9e 100644 --- a/test/truffle/cexts/minimum/ext/minimum/minimum.c +++ b/test/truffle/cexts/minimum/ext/minimum/minimum.c @@ -1,4 +1,5 @@ #include +#include void Init_minimum() { printf("Hello!\n");