Skip to content

Commit eda7198

Browse files
committed
[GR-17457] Tk runtime fixes
PullRequest: truffleruby/3128
2 parents a4f0600 + 01e4fd2 commit eda7198

File tree

11 files changed

+230
-14
lines changed

11 files changed

+230
-14
lines changed

lib/cext/ABI_version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
10
1+
11

lib/truffle/truffle/cext.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -965,13 +965,16 @@ def rb_hash_set_ifnone(hash, value)
965965
ST_CONTINUE = 0
966966
ST_STOP = 1
967967
ST_DELETE = 2
968+
ST_CHECK = 3
969+
ST_REPLACE = 4
968970

969971
def rb_hash_foreach(hash, func, farg)
970972
hash.each do |key, value|
971973
st_result = Truffle::Interop.execute_without_conversion(func, Primitive.cext_wrap(key), Primitive.cext_wrap(value), farg)
972974

973975
case st_result
974976
when ST_CONTINUE
977+
when ST_CHECK
975978
when ST_STOP then break
976979
when ST_DELETE then hash.delete(key)
977980
else raise ArgumentError, "Unknown 'func' return value: #{st_result}"
@@ -1038,8 +1041,8 @@ def rb_protect(function, arg, write_status, status)
10381041
end
10391042

10401043
unless Primitive.object_equal(nil, e)
1041-
store = (Thread.current[:__stored_exceptions__] ||= [])
1042-
pos = store.push(e).size
1044+
store_exception(e)
1045+
pos = extract_tag(e)
10431046
Primitive.thread_set_exception(extract_ruby_exception(e))
10441047
end
10451048

@@ -1049,14 +1052,9 @@ def rb_protect(function, arg, write_status, status)
10491052

10501053
def rb_jump_tag(pos)
10511054
if pos > 0
1052-
store = Thread.current[:__stored_exceptions__]
1053-
if pos == store.size
1054-
e = store.pop
1055-
else
1056-
# Can't disturb other positions or other rb_jump_tag calls might fail.
1057-
e = store[pos - 1]
1058-
store[pos - 1] = nil
1059-
end
1055+
e = retrieve_exception
1056+
tag = extract_tag(e)
1057+
raise RuntimeError, 'mismatch between jump tag and captured exception' unless pos == tag
10601058
raise_exception(e)
10611059
end
10621060
end
@@ -1950,6 +1948,7 @@ def rb_global_variable(obj)
19501948
end
19511949

19521950
GC_REGISTERED_ADDRESSES = {}
1951+
19531952
def rb_gc_register_address(address, obj)
19541953
Truffle::Interop.to_native(address) unless Truffle::Interop.pointer?(address)
19551954
GC_REGISTERED_ADDRESSES[address] = obj
@@ -1959,4 +1958,13 @@ def rb_gc_unregister_address(address)
19591958
Truffle::Interop.to_native(address) unless Truffle::Interop.pointer?(address)
19601959
GC_REGISTERED_ADDRESSES.delete(address)
19611960
end
1961+
1962+
def rb_eval_cmd_kw(cmd, args, kw_splat)
1963+
if (args.size > 0 && kw_splat != 0)
1964+
kwargs = args.pop
1965+
cmd.call(*args, **kwargs)
1966+
else
1967+
cmd.call(*args)
1968+
end
1969+
end
19621970
end

lib/truffle/truffle/cext_preprocessor.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
require_relative 'patches/nokogiri_patches'
1111
require_relative 'patches/oci8_patches'
1212
require_relative 'patches/pg_patches'
13+
require_relative 'patches/tk_patches'
1314

1415
module Truffle
1516
module CExt
@@ -30,6 +31,7 @@ def self.add_gem_patches(patch_hash, gem_patches)
3031
end
3132
processed_patch[:patches] = patch
3233
processed_patch[:gem] = gem
34+
processed_patch[:non_standard_dir_structure] = gem_patches[:non_standard_dir_structure]
3335
raise "Duplicate patch file #{key}." if patch_hash.include?(key)
3436
patch_hash[key] = processed_patch
3537
end
@@ -39,12 +41,17 @@ def self.add_gem_patches(patch_hash, gem_patches)
3941
add_gem_patches(PATCHED_FILES, ::NokogiriPatches::PATCHES)
4042
add_gem_patches(PATCHED_FILES, ::OCI8Patches::PATCHES)
4143
add_gem_patches(PATCHED_FILES, ::PgPatches::PATCHES)
44+
add_gem_patches(PATCHED_FILES, ::TkPatches::PATCHES)
4245

4346
def self.makefile_matcher(command1, command2)
4447
file_list = Hash.new { |h,k| h[k] = [] }
4548
PATCHED_FILES.each_pair do |file, patch|
4649
dir = if patch[:ext_dir]
47-
File.join(patch[:gem], 'ext', patch[:ext_dir])
50+
if patch[:non_standard_dir_structure]
51+
File.join('ext', patch[:ext_dir])
52+
else
53+
File.join(patch[:gem], 'ext', patch[:ext_dir])
54+
end
4855
else
4956
"/#{patch[:gem]}"
5057
end
@@ -85,7 +92,11 @@ def self.makefile_matcher(command1, command2)
8592
def self.patch(file, contents, directory)
8693
if patched_file = PATCHED_FILES[File.basename(file)]
8794
matched = if patched_file[:ext_dir]
88-
directory.end_with?(File.join(patched_file[:gem], 'ext', patched_file[:ext_dir]))
95+
if patched_file[:non_standard_dir_structure]
96+
directory.end_with?('ext', patched_file[:ext_dir])
97+
else
98+
directory.end_with?(File.join(patched_file[:gem], 'ext', patched_file[:ext_dir]))
99+
end
89100
else
90101
regexp = /^#{Regexp.escape(patched_file[:gem])}\b/
91102
directory.split('/').last(3).any? { |part| part =~ regexp } || file.split('/').last(2).any? { |part| part =~ regexp }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. This
2+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
3+
# redistribute it and/or modify it under the terms of the:
4+
#
5+
# Eclipse Public License version 2.0, or
6+
# GNU General Public License version 2, or
7+
# GNU Lesser General Public License version 2.1.
8+
9+
# Tested with tk version 0.4.0
10+
class TkPatches
11+
12+
PATCHES = {
13+
gem: 'tk',
14+
non_standard_dir_structure: true,
15+
patches: {
16+
['tk/tkutil', 'tkutil.c'] => [
17+
{
18+
match: 'st_foreach_check(RHASH_TBL(keys), to_strkey, new_keys, Qundef)',
19+
replacement: 'rb_hash_foreach(keys, to_strkey, new_keys)'
20+
},
21+
{
22+
match: 'st_foreach_check(RHASH_TBL(hash), push_kv, args, Qundef)',
23+
replacement: 'rb_hash_foreach(hash, push_kv, args)'
24+
},
25+
{
26+
match: 'st_foreach_check(RHASH_TBL(hash), push_kv_enc, args, Qundef)',
27+
replacement: 'rb_hash_foreach(hash, push_kv_enc, args)'
28+
},
29+
]
30+
}
31+
}
32+
33+
end

spec/ruby/optional/capi/ext/kernel_spec.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ VALUE kernel_spec_rb_eval_string(VALUE self, VALUE str) {
112112
return rb_eval_string(RSTRING_PTR(str));
113113
}
114114

115+
VALUE kernel_spec_rb_eval_cmd_kw(VALUE self, VALUE cmd, VALUE args, VALUE kw_splat) {
116+
return rb_eval_cmd_kw(cmd, args, NUM2INT(kw_splat));
117+
}
118+
115119
VALUE kernel_spec_rb_raise(VALUE self, VALUE hash) {
116120
rb_hash_aset(hash, ID2SYM(rb_intern("stage")), ID2SYM(rb_intern("before")));
117121
if (self != Qundef)
@@ -361,6 +365,7 @@ void Init_kernel_spec(void) {
361365
rb_define_method(cls, "rb_frame_this_func_test_again", kernel_spec_rb_frame_this_func, 0);
362366
rb_define_method(cls, "rb_ensure", kernel_spec_rb_ensure, 4);
363367
rb_define_method(cls, "rb_eval_string", kernel_spec_rb_eval_string, 1);
368+
rb_define_method(cls, "rb_eval_cmd_kw", kernel_spec_rb_eval_cmd_kw, 3);
364369
rb_define_method(cls, "rb_raise", kernel_spec_rb_raise, 1);
365370
rb_define_method(cls, "rb_throw", kernel_spec_rb_throw, 1);
366371
rb_define_method(cls, "rb_throw_obj", kernel_spec_rb_throw_obj, 2);

spec/ruby/optional/capi/kernel_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,25 @@ def proc_caller
504504
end
505505
end
506506

507+
describe "rb_eval_cmd_kw" do
508+
it "evaluates a string of ruby code" do
509+
@s.rb_eval_cmd_kw("1+1", [], 0).should == 2
510+
end
511+
512+
it "calls a proc with the supplied arguments" do
513+
@s.rb_eval_cmd_kw(-> (*x) { x.map { |i| i + 1 } }, [1, 3, 7], 0).should == [2, 4, 8]
514+
end
515+
516+
it "calls a proc with keyword arguments if kw_splat is non zero" do
517+
a_proc = -> (*x, **y) {
518+
res = x.map { |i| i + 1 }
519+
y.each { |k, v| res << k; res << v }
520+
res
521+
}
522+
@s.rb_eval_cmd_kw(a_proc, [1, 3, 7, {a: 1, b: 2, c: 3}], 1).should == [2, 4, 8, :a, 1, :b, 2, :c, 3]
523+
end
524+
end
525+
507526
describe "rb_block_proc" do
508527
it "converts the implicit block into a proc" do
509528
proc = @s.rb_block_proc { 1+1 }

src/main/c/cext/call.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,11 @@ int rb_method_boundp(VALUE klass, ID id, int ex) {
228228
VALUE rb_exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg) {
229229
return rb_tr_wrap(polyglot_invoke(RUBY_CEXT, "rb_exec_recursive", func, rb_tr_unwrap(obj), rb_tr_unwrap(arg)));
230230
}
231+
232+
VALUE rb_eval_cmd_kw(VALUE cmd, VALUE args, int kw_splat) {
233+
if (!RB_TYPE_P(cmd, T_STRING)) {
234+
return RUBY_CEXT_INVOKE("rb_eval_cmd_kw", cmd, args, INT2NUM(kw_splat));
235+
} else {
236+
return RUBY_CEXT_INVOKE("rb_eval_string", cmd);
237+
}
238+
}

src/main/java/org/truffleruby/cext/CExtNodes.java

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@
9292
import org.truffleruby.language.constants.LookupConstantNode;
9393
import org.truffleruby.language.control.BreakException;
9494
import org.truffleruby.language.control.BreakID;
95+
import org.truffleruby.language.control.DynamicReturnException;
96+
import org.truffleruby.language.control.LocalReturnException;
97+
import org.truffleruby.language.control.NextException;
98+
import org.truffleruby.language.control.RedoException;
99+
import org.truffleruby.language.control.RetryException;
95100
import org.truffleruby.language.control.RaiseException;
101+
import org.truffleruby.language.control.ThrowException;
96102
import org.truffleruby.language.dispatch.DispatchNode;
97103
import org.truffleruby.language.dispatch.LiteralCallNode;
98104
import org.truffleruby.language.library.RubyStringLibrary;
@@ -111,6 +117,7 @@
111117
import com.oracle.truffle.api.Truffle;
112118
import com.oracle.truffle.api.dsl.Cached;
113119
import com.oracle.truffle.api.dsl.CreateCast;
120+
import com.oracle.truffle.api.dsl.Fallback;
114121
import com.oracle.truffle.api.dsl.NodeChild;
115122
import com.oracle.truffle.api.dsl.ReportPolymorphism;
116123
import com.oracle.truffle.api.dsl.Specialization;
@@ -130,6 +137,18 @@
130137
@CoreModule("Truffle::CExt")
131138
public class CExtNodes {
132139

140+
/* These tag values are derived from MRI source and from the Tk gem and are used to represent different control flow
141+
* states under which code may exit an `rb_protect` block. The fatal tag is defined but I could not find a point
142+
* where it is assigned, and am not sure it maps to anything we would use in TruffleRuby. */
143+
public static final int RUBY_TAG_RETURN = 0x1;
144+
public static final int RUBY_TAG_BREAK = 0x2;
145+
public static final int RUBY_TAG_NEXT = 0x3;
146+
public static final int RUBY_TAG_RETRY = 0x4;
147+
public static final int RUBY_TAG_REDO = 0x5;
148+
public static final int RUBY_TAG_RAISE = 0x6;
149+
public static final int RUBY_TAG_THROW = 0x7;
150+
public static final int RUBY_TAG_FATAL = 0x8;
151+
133152
public static Pointer newNativeStringPointer(int capacity, RubyLanguage language) {
134153
// We need up to 4 \0 bytes for UTF-32. Always use 4 for speed rather than checking the encoding min length.
135154
Pointer pointer = Pointer.mallocAutoRelease(capacity + 4, language);
@@ -1355,6 +1374,31 @@ protected Object executeWithProtect(RubyProc block,
13551374
}
13561375
}
13571376

1377+
@CoreMethod(names = "store_exception", onSingleton = true, required = 1)
1378+
public abstract static class StoreException extends YieldingCoreMethodNode {
1379+
1380+
@Specialization
1381+
protected Object storeException(CapturedException captured) {
1382+
final ExtensionCallStack extensionStack = getLanguage()
1383+
.getCurrentThread()
1384+
.getCurrentFiber().extensionCallStack;
1385+
extensionStack.setException(captured);
1386+
return nil;
1387+
}
1388+
}
1389+
1390+
@CoreMethod(names = "retrieve_exception", onSingleton = true)
1391+
public abstract static class RetrieveException extends YieldingCoreMethodNode {
1392+
1393+
@Specialization
1394+
protected Object retrieveException() {
1395+
final ExtensionCallStack extensionStack = getLanguage()
1396+
.getCurrentThread()
1397+
.getCurrentFiber().extensionCallStack;
1398+
return extensionStack.getException();
1399+
}
1400+
}
1401+
13581402
@CoreMethod(names = "extract_ruby_exception", onSingleton = true, required = 1)
13591403
public abstract static class ExtractRubyException extends CoreMethodArrayArgumentsNode {
13601404

@@ -1370,6 +1414,66 @@ protected Object executeThrow(CapturedException captured,
13701414
}
13711415
}
13721416

1417+
@CoreMethod(names = "extract_tag", onSingleton = true, required = 1)
1418+
public abstract static class ExtractRubyTag extends CoreMethodArrayArgumentsNode {
1419+
1420+
@Specialization
1421+
protected int executeThrow(CapturedException captured,
1422+
@Cached ExtractRubyTagHelperNode helperNode) {
1423+
return helperNode.execute(captured.getException());
1424+
}
1425+
}
1426+
1427+
public abstract static class ExtractRubyTagHelperNode extends RubyBaseNode {
1428+
1429+
public abstract int execute(Throwable e);
1430+
1431+
@Specialization
1432+
protected int dynamicReturnTag(DynamicReturnException e) {
1433+
return RUBY_TAG_RETURN;
1434+
}
1435+
1436+
@Specialization
1437+
protected int localReturnTag(LocalReturnException e) {
1438+
return RUBY_TAG_RETURN;
1439+
}
1440+
1441+
@Specialization
1442+
protected int breakTag(BreakException e) {
1443+
return RUBY_TAG_BREAK;
1444+
}
1445+
1446+
@Specialization
1447+
protected int nextTag(NextException e) {
1448+
return RUBY_TAG_NEXT;
1449+
}
1450+
1451+
@Specialization
1452+
protected int retryTag(RetryException e) {
1453+
return RUBY_TAG_RETRY;
1454+
}
1455+
1456+
@Specialization
1457+
protected int redoTag(RedoException e) {
1458+
return RUBY_TAG_REDO;
1459+
}
1460+
1461+
@Specialization
1462+
protected int raiseTag(RaiseException e) {
1463+
return RUBY_TAG_RAISE;
1464+
}
1465+
1466+
@Specialization
1467+
protected int throwTag(ThrowException e) {
1468+
return RUBY_TAG_THROW;
1469+
}
1470+
1471+
@Fallback
1472+
protected int noTag(Throwable e) {
1473+
return 0;
1474+
}
1475+
}
1476+
13731477
@CoreMethod(names = "raise_exception", onSingleton = true, required = 1)
13741478
public abstract static class RaiseExceptionNode extends CoreMethodArrayArgumentsNode {
13751479

src/main/java/org/truffleruby/core/MarkingService.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import org.truffleruby.RubyContext;
1616
import org.truffleruby.RubyLanguage;
17+
import org.truffleruby.cext.CapturedException;
1718
import org.truffleruby.cext.ValueWrapperManager;
1819
import org.truffleruby.core.array.ArrayUtils;
1920
import org.truffleruby.core.queue.UnsizedQueue;
@@ -118,6 +119,7 @@ protected static class ExtensionCallStackEntry {
118119
protected final boolean keywordsGiven;
119120
protected Object specialVariables;
120121
protected final Object block;
122+
protected CapturedException capturedException;
121123

122124
protected ExtensionCallStackEntry(
123125
ExtensionCallStackEntry previous,
@@ -128,6 +130,7 @@ protected ExtensionCallStackEntry(
128130
this.keywordsGiven = keywordsGiven;
129131
this.specialVariables = specialVariables;
130132
this.block = block;
133+
this.capturedException = null;
131134
}
132135
}
133136

@@ -165,6 +168,14 @@ public void setSpecialVariables(Object specialVariables) {
165168
current.specialVariables = specialVariables;
166169
}
167170

171+
public CapturedException getException() {
172+
return current.capturedException;
173+
}
174+
175+
public void setException(CapturedException capturedException) {
176+
current.capturedException = capturedException;
177+
}
178+
168179
public Object getBlock() {
169180
return current.block;
170181
}

0 commit comments

Comments
 (0)