Skip to content

Commit 0e8afc0

Browse files
Merge pull request #4 from ruby/katei/fix-null-func-call
2 parents 27b1c14 + 774be71 commit 0e8afc0

File tree

7 files changed

+163
-51
lines changed

7 files changed

+163
-51
lines changed

.clang-format

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
Language: Cpp
33
BasedOnStyle: LLVM
4+
IndentPPDirectives: AfterHash
45
---
56
Language: JavaScript
67
DisableFormat: true

Rakefile

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ BUILD_SOURCES = [
1414
FULL_EXTS = "bigdecimal,cgi/escape,continuation,coverage,date,dbm,digest/bubblebabble,digest,digest/md5,digest/rmd160,digest/sha1,digest/sha2,etc,fcntl,fiber,gdbm,json,json/generator,json/parser,nkf,objspace,pathname,psych,racc/cparse,rbconfig/sizeof,ripper,stringio,strscan,monitor"
1515

1616
BUILD_PROFILES = {
17-
"minimal" => { default_exts: "", user_exts: [] },
18-
"minimal-js" => { default_exts: "", user_exts: ["js", "witapi"] },
19-
"full" => { default_exts: FULL_EXTS, user_exts: [] },
20-
"full-js" => { default_exts: FULL_EXTS, user_exts: ["js", "witapi"] },
17+
"minimal" => { debug: false, default_exts: "", user_exts: [] },
18+
"minimal-js" => { debug: false, default_exts: "", user_exts: ["js", "witapi"] },
19+
"full" => { debug: false, default_exts: FULL_EXTS, user_exts: [] },
20+
"full-js" => { debug: false, default_exts: FULL_EXTS, user_exts: ["js", "witapi"] },
2121
}
2222

2323
BUILDS = [
@@ -128,7 +128,13 @@ class BuildPlan
128128
default_exts = profile[:default_exts]
129129
user_exts = profile[:user_exts]
130130

131-
ldflags = %w(-Xlinker -zstack-size=16777216)
131+
ldflags = if profile[:debug]
132+
# use --stack-first to detect stack overflow easily
133+
%w(-Xlinker --stack-first -Xlinker -z -Xlinker stack-size=16777216)
134+
else
135+
%w(-Xlinker -zstack-size=16777216)
136+
end
137+
132138
xldflags = []
133139

134140
args = ["--host", target, "--build", build_triple]
@@ -153,7 +159,12 @@ class BuildPlan
153159

154160
args << %Q(LDFLAGS="#{ldflags.join(" ")}")
155161
args << %Q(XLDFLAGS="#{xldflags.join(" ")}")
156-
args << %Q(debugflags="-g0")
162+
if profile[:debug]
163+
args << %Q(debugflags="-g")
164+
args << %Q(wasmoptflags="-O2 -g")
165+
else
166+
args << %Q(debugflags="-g0")
167+
end
157168
args << "--disable-install-doc"
158169
args
159170
end

ext/witapi/bindgen/rb-abi-guest.wit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ rb-eval-string-protect: function(str: string) -> tuple<rb-abi-value, s32>
1313
rb-funcallv-protect: function(recv: rb-abi-value, mid: rb-id, args: list<rb-abi-value>) -> tuple<rb-abi-value, s32>
1414
rb-intern: function(name: string) -> rb-id
1515
rb-errinfo: function() -> rb-abi-value
16+
rb-clear-errinfo: function()
1617

1718
rstring-ptr: function(value: rb-abi-value) -> string

ext/witapi/witapi-core.c

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include "ruby.h"
44

5+
#define TAG_NONE 0
6+
57
#include "bindgen/rb-abi-guest.h"
68

79
__attribute__((import_module("asyncify"), import_name("start_unwind"))) void
@@ -18,6 +20,13 @@ void *rb_wasm_handle_scan_unwind(void);
1820
void *rb_wasm_handle_fiber_unwind(void (**new_fiber_entry)(void *, void *),
1921
void **arg0, void **arg1,
2022
bool *is_new_fiber_started);
23+
#define RB_WASM_ENABLE_DEBUG_LOG 0
24+
25+
#if RB_WASM_ENABLE_DEBUG_LOG
26+
# define RB_WASM_DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)
27+
#else
28+
# define RB_WASM_DEBUG_LOG(...) (void)0
29+
#endif
2130

2231
#define RB_WASM_LIB_RT(MAIN_ENTRY) \
2332
{ \
@@ -67,30 +76,49 @@ void *rb_wasm_handle_fiber_unwind(void (**new_fiber_entry)(void *, void *),
6776
}
6877

6978
static VALUE rb_abi_guest_arena_hash;
70-
static void rb_abi_lend_object_internal(VALUE obj) {
71-
VALUE ref_count = rb_hash_lookup(rb_abi_guest_arena_hash, obj);
79+
static VALUE rb_abi_guest_refcount_hash;
80+
81+
static VALUE rb_abi_lend_object_internal(VALUE obj) {
82+
VALUE object_id = rb_obj_id(obj);
83+
VALUE ref_count = rb_hash_lookup(rb_abi_guest_refcount_hash, object_id);
7284
if (NIL_P(ref_count)) {
73-
rb_hash_aset(rb_abi_guest_arena_hash, obj, INT2FIX(1));
85+
rb_hash_aset(rb_abi_guest_arena_hash, object_id, obj);
86+
rb_hash_aset(rb_abi_guest_refcount_hash, object_id, INT2FIX(1));
7487
} else {
75-
rb_hash_aset(rb_abi_guest_arena_hash, obj, INT2FIX(FIX2INT(ref_count) + 1));
88+
rb_hash_aset(rb_abi_guest_refcount_hash, object_id, INT2FIX(FIX2INT(ref_count) + 1));
7689
}
90+
return Qundef;
7791
}
7892
static void rb_abi_lend_object(VALUE obj) {
79-
RB_WASM_LIB_RT(rb_abi_lend_object_internal(obj));
93+
RB_WASM_DEBUG_LOG("rb_abi_lend_object: obj = %p\n", (void *)obj);
94+
int state;
95+
RB_WASM_LIB_RT(rb_protect(rb_abi_lend_object_internal, obj, &state));
96+
assert(state == TAG_NONE && "rb_abi_lend_object_internal failed");
8097
}
8198

82-
void rb_abi_guest_rb_abi_value_dtor(void *data) {
83-
VALUE obj = (VALUE)data;
84-
VALUE ref_count = rb_hash_lookup(rb_abi_guest_arena_hash, obj);
99+
static VALUE rb_abi_guest_rb_abi_value_dtor_internal(VALUE obj) {
100+
VALUE object_id = rb_obj_id(obj);
101+
VALUE ref_count = rb_hash_lookup(rb_abi_guest_refcount_hash, object_id);
85102
if (NIL_P(ref_count)) {
86103
rb_warning("rb_abi_guest_rb_abi_value_dtor: double free detected");
87-
return;
104+
return Qundef;
88105
}
89106
if (ref_count == INT2FIX(1)) {
90-
rb_hash_delete(rb_abi_guest_arena_hash, obj);
107+
RB_WASM_DEBUG_LOG("rb_abi_guest_rb_abi_value_dtor: ref_count == 1\n");
108+
rb_hash_delete(rb_abi_guest_refcount_hash, object_id);
109+
rb_hash_delete(rb_abi_guest_arena_hash, object_id);
91110
} else {
92-
rb_hash_aset(rb_abi_guest_arena_hash, obj, INT2FIX(FIX2INT(ref_count) - 1));
111+
RB_WASM_DEBUG_LOG("rb_abi_guest_rb_abi_value_dtor: ref_count = %d\n", FIX2INT(ref_count));
112+
rb_hash_aset(rb_abi_guest_refcount_hash, object_id, INT2FIX(FIX2INT(ref_count) - 1));
93113
}
114+
return Qundef;
115+
}
116+
117+
void rb_abi_guest_rb_abi_value_dtor(void *data) {
118+
RB_WASM_DEBUG_LOG("rb_abi_guest_rb_abi_value_dtor: data = %p\n", data);
119+
int state;
120+
RB_WASM_LIB_RT(rb_protect(rb_abi_guest_rb_abi_value_dtor_internal, (VALUE)data, &state));
121+
assert(state == TAG_NONE && "rb_abi_guest_rb_abi_value_dtor_internal failed");
94122
}
95123

96124
// MARK: - Exported functions
@@ -103,7 +131,10 @@ void rb_abi_guest_ruby_init(void) {
103131
RB_WASM_LIB_RT(ruby_init())
104132

105133
rb_abi_guest_arena_hash = rb_hash_new();
134+
rb_abi_guest_refcount_hash = rb_hash_new();
135+
106136
rb_gc_register_mark_object(rb_abi_guest_arena_hash);
137+
rb_gc_register_mark_object(rb_abi_guest_refcount_hash);
107138
}
108139

109140
void rb_abi_guest_ruby_sysinit(rb_abi_guest_list_string_t *args) {
@@ -142,9 +173,13 @@ void rb_abi_guest_rb_eval_string_protect(rb_abi_guest_string_t *str,
142173
rb_abi_guest_rb_abi_value_t *result,
143174
int32_t *state) {
144175
VALUE retval;
176+
RB_WASM_DEBUG_LOG("rb_eval_string_protect: str = %s\n", str->ptr);
145177
RB_WASM_LIB_RT(retval = rb_eval_string_protect(str->ptr, state));
146-
rb_abi_lend_object(retval);
178+
RB_WASM_DEBUG_LOG("rb_eval_string_protect: retval = %p, state = %d\n", (void *)retval, *state);
147179

180+
if (*state == TAG_NONE) {
181+
rb_abi_lend_object(retval);
182+
}
148183
*result = rb_abi_guest_rb_abi_value_new((void *)retval);
149184
}
150185

@@ -173,7 +208,11 @@ void rb_abi_guest_rb_funcallv_protect(rb_abi_guest_rb_abi_value_t recv,
173208
struct rb_funcallv_thunk_ctx ctx = {
174209
.recv = r_recv, .mid = mid, .args = args};
175210
RB_WASM_LIB_RT(retval = rb_protect(rb_funcallv_thunk, (VALUE)&ctx, ret1));
176-
rb_abi_lend_object(retval);
211+
RB_WASM_DEBUG_LOG("rb_abi_guest_rb_funcallv_protect: retval = %p, state = %d\n", (void *)retval, *ret1);
212+
213+
if (*ret1 == TAG_NONE) {
214+
rb_abi_lend_object(retval);
215+
}
177216
*ret0 = rb_abi_guest_rb_abi_value_new((void *)retval);
178217
}
179218

@@ -188,6 +227,10 @@ rb_abi_guest_rb_abi_value_t rb_abi_guest_rb_errinfo(void) {
188227
return rb_abi_guest_rb_abi_value_new((void *)retval);
189228
}
190229

230+
void rb_abi_guest_rb_clear_errinfo(void) {
231+
rb_set_errinfo(Qnil);
232+
}
233+
191234
void rb_abi_guest_rstring_ptr(rb_abi_guest_rb_abi_value_t value,
192235
rb_abi_guest_string_t *ret0) {
193236
VALUE r_str = (VALUE)rb_abi_guest_rb_abi_value_get(&value);

packages/npm-packages/ruby-wasm-wasi/build-package.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ruby_root="$1"
1414
package_dir="$(cd "$(dirname "$0")" && pwd)"
1515
dist_dir="$package_dir/dist"
1616
repo_dir="$package_dir/../../../"
17+
BUILD_WITH_DEBUG="${BUILD_WITH_DEBUG-""}"
1718

1819
rm -rf "$dist_dir"
1920

@@ -28,7 +29,11 @@ wit-bindgen js \
2829
npx tsc --build
2930
)
3031

31-
wasm-opt --strip-debug "$ruby_root/usr/local/bin/ruby" -o "$dist_dir/ruby.wasm"
32+
if [ -z "$BUILD_WITH_DEBUG" ]; then
33+
wasm-opt --strip-debug "$ruby_root/usr/local/bin/ruby" -o "$dist_dir/ruby.wasm"
34+
else
35+
cp "$ruby_root/usr/local/bin/ruby" "$dist_dir/ruby.wasm"
36+
fi
3237

3338
mkdir "$dist_dir/bindgen"
3439
cp $(find "$package_dir/src/bindgen" -name "*.js" -or -name "*.d.ts") "$dist_dir/bindgen"

packages/npm-packages/ruby-wasm-wasi/src/index.ts

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ export class RubyVM {
2323
guest: RbAbi.RbAbiGuest;
2424
private instance: WebAssembly.Instance | null = null;
2525
private exporter: JsValueExporter;
26+
private exceptionFormatter: RbExceptionFormatter;
2627

2728
constructor() {
2829
this.guest = new RbAbi.RbAbiGuest();
2930
this.exporter = new JsValueExporter();
31+
this.exceptionFormatter = new RbExceptionFormatter();
3032
}
3133

3234
/**
@@ -168,7 +170,7 @@ export class RubyVM {
168170
*
169171
*/
170172
eval(code: string): RbValue {
171-
return evalRbCode(this, this.exporter, code);
173+
return evalRbCode(this, { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter }, code);
172174
}
173175
}
174176

@@ -189,7 +191,7 @@ export class RbValue {
189191
constructor(
190192
private inner: RbAbi.RbAbiValue,
191193
private vm: RubyVM,
192-
private exporter: JsValueExporter
194+
private privateObject: RubyVMPrivate
193195
) {}
194196

195197
/**
@@ -207,9 +209,9 @@ export class RbValue {
207209
call(callee: string, ...args: RbValue[]): RbValue {
208210
const innerArgs = args.map((arg) => arg.inner);
209211
return new RbValue(
210-
callRbMethod(this.vm, this.exporter, this.inner, callee, innerArgs),
212+
callRbMethod(this.vm, this.privateObject, this.inner, callee, innerArgs),
211213
this.vm,
212-
this.exporter
214+
this.privateObject
213215
);
214216
}
215217

@@ -231,7 +233,7 @@ export class RbValue {
231233
toString(): string {
232234
const rbString = callRbMethod(
233235
this.vm,
234-
this.exporter,
236+
this.privateObject,
235237
this.inner,
236238
"to_s",
237239
[]
@@ -252,7 +254,7 @@ export class RbValue {
252254
return null;
253255
}
254256
jsValue.call("__export_to_js");
255-
return this.exporter.exportJsValue();
257+
return this.privateObject.exporter.exportJsValue();
256258
}
257259
}
258260

@@ -269,18 +271,52 @@ enum ruby_tag_type {
269271
Mask = 0xf,
270272
}
271273

272-
const formatException = (
273-
klass: string,
274-
message: string,
275-
backtrace: [string, string]
276-
) => {
277-
return `${backtrace[0]}: ${message} (${klass})\n${backtrace[1]}`;
274+
type RubyVMPrivate = {
275+
exporter: JsValueExporter,
276+
exceptionFormatter: RbExceptionFormatter,
278277
};
279278

279+
280+
class RbExceptionFormatter {
281+
private literalsCache: [RbValue, RbValue, RbValue] | null = null;
282+
283+
format(error: RbValue, vm: RubyVM, privateObject: RubyVMPrivate): string {
284+
const [zeroLiteral, oneLiteral, newLineLiteral] = (() => {
285+
if (this.literalsCache == null) {
286+
const zeroOneNewLine: [RbValue, RbValue, RbValue] = [
287+
evalRbCode(vm, privateObject, "0"),
288+
evalRbCode(vm, privateObject, "1"),
289+
evalRbCode(vm, privateObject, `"\n"`)
290+
];
291+
this.literalsCache = zeroOneNewLine;
292+
return zeroOneNewLine;
293+
} else {
294+
return this.literalsCache;
295+
}
296+
})();
297+
298+
const backtrace = error.call("backtrace");
299+
const firstLine = backtrace.call("at", zeroLiteral);
300+
const restLines = backtrace.call("drop", oneLiteral).call("join", newLineLiteral);
301+
return this.formatString(error.call("class").toString(), error.toString(), [
302+
firstLine.toString(),
303+
restLines.toString(),
304+
])
305+
}
306+
307+
formatString(
308+
klass: string,
309+
message: string,
310+
backtrace: [string, string]
311+
): string {
312+
return `${backtrace[0]}: ${message} (${klass})\n${backtrace[1]}`;
313+
};
314+
}
315+
280316
const checkStatusTag = (
281317
rawTag: number,
282318
vm: RubyVM,
283-
exporter: JsValueExporter
319+
privateObject: RubyVMPrivate
284320
) => {
285321
switch (rawTag & ruby_tag_type.Mask) {
286322
case ruby_tag_type.None:
@@ -299,40 +335,34 @@ const checkStatusTag = (
299335
throw new RbError("unexpected throw");
300336
case ruby_tag_type.Raise:
301337
case ruby_tag_type.Fatal:
302-
const error = new RbValue(vm.guest.rbErrinfo(), vm, exporter);
303-
const newLine = evalRbCode(vm, exporter, `"\n"`);
304-
const backtrace = error.call("backtrace");
305-
const firstLine = backtrace.call("at", evalRbCode(vm, exporter, "0"));
306-
const restLines = backtrace
307-
.call("drop", evalRbCode(vm, exporter, "1"))
308-
.call("join", newLine);
309-
throw new RbError(
310-
formatException(error.call("class").toString(), error.toString(), [
311-
firstLine.toString(),
312-
restLines.toString(),
313-
])
314-
);
338+
const error = new RbValue(vm.guest.rbErrinfo(), vm, privateObject);
339+
if (error.call("nil?").toString() === "true") {
340+
throw new RbError("no exception object");
341+
}
342+
// clear errinfo if got exception due to no rb_jump_tag
343+
vm.guest.rbClearErrinfo();
344+
throw new RbError(privateObject.exceptionFormatter.format(error, vm, privateObject));
315345
default:
316346
throw new RbError(`unknown error tag: ${rawTag}`);
317347
}
318348
};
319349

320350
const callRbMethod = (
321351
vm: RubyVM,
322-
exporter: JsValueExporter,
352+
privateObject: RubyVMPrivate,
323353
recv: RbAbi.RbAbiValue,
324354
callee: string,
325355
args: RbAbi.RbAbiValue[]
326356
) => {
327357
const mid = vm.guest.rbIntern(callee + "\0");
328358
const [value, status] = vm.guest.rbFuncallvProtect(recv, mid, args);
329-
checkStatusTag(status, vm, exporter);
359+
checkStatusTag(status, vm, privateObject);
330360
return value;
331361
};
332-
const evalRbCode = (vm: RubyVM, exporter: JsValueExporter, code: string) => {
362+
const evalRbCode = (vm: RubyVM, privateObject: RubyVMPrivate, code: string) => {
333363
const [value, status] = vm.guest.rbEvalStringProtect(code + "\0");
334-
checkStatusTag(status, vm, exporter);
335-
return new RbValue(value, vm, exporter);
364+
checkStatusTag(status, vm, privateObject);
365+
return new RbValue(value, vm, privateObject);
336366
};
337367

338368
/**

0 commit comments

Comments
 (0)