Skip to content

Commit 6199466

Browse files
authored
testsuite: multi value (#76)
* test: remove skipping of multivalue * fix: remove > 1 result check * fix: for multi-value we need expected reversed * refactor: tests that use invoke now use invokeDynamic - This is because of the complication of multiple return values * refactor: rename invokeDynamic -> invoke
1 parent fb548cd commit 6199466

File tree

3 files changed

+67
-124
lines changed

3 files changed

+67
-124
lines changed

Diff for: src/instance.zig

+4-88
Original file line numberDiff line numberDiff line change
@@ -227,94 +227,10 @@ pub const Instance = struct {
227227
return try self.store.global(handle);
228228
}
229229

230-
// invoke:
231-
// 1. Lookup our function by name with getExport
232-
// 2. Get the function type signature
233-
// 3. Check that the incoming arguments in `args` match the function signature
234-
// 4. Check that the return type in `Result` matches the function signature
235-
// 5. Get the code for our function
236-
// 6. Set up the stacks (operand stack, control stack)
237-
// 7. Push a control frame and our parameters
238-
// 8. Execute our function
239-
// 9. Pop our result and return it
240-
pub fn invoke(self: *Instance, name: []const u8, args: anytype, comptime Result: type, comptime options: InterpreterOptions) !Result {
241-
// 1.
242-
const index = try self.module.getExport(.Func, name);
243-
if (index >= self.module.functions.list.items.len) return error.FuncIndexExceedsTypesLength;
244-
245-
const function = self.module.functions.list.items[index];
246-
247-
// 2.
248-
const func_type = self.module.types.list.items[function.typeidx];
249-
const params = self.module.value_types.list.items[func_type.params_offset .. func_type.params_offset + func_type.params_count];
250-
const results = self.module.value_types.list.items[func_type.results_offset .. func_type.results_offset + func_type.results_count];
251-
252-
if (params.len != args.len) return error.ParamCountMismatch;
253-
254-
// 3. check the types of params
255-
inline for (args) |arg, i| {
256-
if (params[i] != common.toValueType(@TypeOf(arg))) return error.ParamTypeMismatch;
257-
}
258-
259-
// 4. check the result type
260-
if (results.len > 1) return error.OnlySingleReturnValueSupported;
261-
if (Result != void and results.len == 1) {
262-
if (results[0] != common.toValueType(Result)) return error.ResultTypeMismatch;
263-
}
264-
265-
// 5. get the function bytecode
266-
const code = self.module.codes.list.items[index];
267-
268-
// 6. set up our stacks
269-
var op_stack_mem: [options.operand_stack_size]u64 = [_]u64{0} ** options.operand_stack_size;
270-
var frame_stack_mem: [options.control_stack_size]Interpreter.Frame = [_]Interpreter.Frame{undefined} ** options.control_stack_size;
271-
var label_stack_mem: [options.label_stack_size]Interpreter.Label = [_]Interpreter.Label{undefined} ** options.control_stack_size;
272-
var interp = Interpreter.init(op_stack_mem[0..], frame_stack_mem[0..], label_stack_mem[0..], self);
273-
274-
// I think everything below here should probably live in interpret
275-
276-
const locals_start = interp.op_stack.len;
277-
278-
// 7b. push params
279-
inline for (args) |arg, i| {
280-
try interp.pushOperand(@TypeOf(arg), arg);
281-
}
282-
283-
// 7c. push (i.e. make space for) locals
284-
var i: usize = 0;
285-
while (i < code.locals_count) : (i += 1) {
286-
try interp.pushOperand(u64, 0);
287-
}
288-
289-
// 7a. push control frame
290-
try interp.pushFrame(Interpreter.Frame{
291-
.op_stack_len = locals_start,
292-
.label_stack_len = interp.label_stack.len,
293-
.return_arity = results.len,
294-
.inst = self,
295-
}, code.locals_count + params.len);
296-
297-
// 7a.2. push label for our implicit function block. We know we don't have
298-
// any code to execute after calling invoke, but we will need to
299-
// pop a Label
300-
try interp.pushLabel(Interpreter.Label{
301-
.return_arity = results.len,
302-
.op_stack_len = locals_start,
303-
.continuation = code.code[0..0],
304-
});
305-
306-
// 8. Execute our function
307-
try interp.invoke(code.code);
308-
309-
// 9.
310-
if (Result == void) return;
311-
return try interp.popOperand(Result);
312-
}
313-
314-
// invokeDynamic
230+
// invoke
315231
//
316232
// Similar to invoke, but without some type checking
317-
pub fn invokeDynamic(self: *Instance, name: []const u8, in: []u64, out: []u64, comptime options: InterpreterOptions) !void {
233+
pub fn invoke(self: *Instance, name: []const u8, in: []u64, out: []u64, comptime options: InterpreterOptions) !void {
318234
// 1.
319235
const index = try self.module.getExport(.Func, name);
320236
if (index >= self.module.functions.list.items.len) return error.FuncIndexExceedsTypesLength;
@@ -328,7 +244,7 @@ pub const Instance = struct {
328244
switch (function) {
329245
.function => |f| {
330246
if (f.params.len != in.len) return error.ParamCountMismatch;
331-
if (f.results.len > 1) return error.OnlySingleReturnValueSupported;
247+
if (f.results.len != out.len) return error.ResultCountMismatch;
332248

333249
// 6. set up our stacks
334250
var interp = Interpreter.init(op_stack_mem[0..], frame_stack_mem[0..], label_stack_mem[0..], f.instance);
@@ -363,7 +279,7 @@ pub const Instance = struct {
363279
.continuation = f.code[0..0],
364280
});
365281

366-
// std.debug.warn("invokeDynamic[{}, {}] = {x}\n", .{ index, handle, f.code });
282+
// std.debug.warn("invoke[{}, {}] = {x}\n", .{ index, handle, f.code });
367283
// 8. Execute our function
368284
try interp.invoke(f.code);
369285

Diff for: src/module.zig

+52-14
Original file line numberDiff line numberDiff line change
@@ -696,8 +696,10 @@ test "module loading (simple add function)" {
696696
var inst = Instance.init(&arena.allocator, &store, module);
697697
try inst.instantiate();
698698

699-
const result = try inst.invoke("add", .{ @as(i32, 22), @as(i32, 23) }, i32, .{});
700-
testing.expectEqual(@as(i32, 45), result);
699+
var in = [2]u64{ 22, 23 };
700+
var out = [1]u64{0};
701+
try inst.invoke("add", in[0..], out[0..], .{});
702+
testing.expectEqual(@as(i32, 45), @bitCast(i32, @truncate(u32, out[0])));
701703
}
702704

703705
test "module loading (fib)" {
@@ -715,13 +717,34 @@ test "module loading (fib)" {
715717
var inst = Instance.init(&arena.allocator, &store, module);
716718
try inst.instantiate();
717719

718-
testing.expectEqual(@as(i32, 1), try inst.invoke("fib", .{@as(i32, 0)}, i32, .{}));
719-
testing.expectEqual(@as(i32, 1), try inst.invoke("fib", .{@as(i32, 1)}, i32, .{}));
720-
testing.expectEqual(@as(i32, 2), try inst.invoke("fib", .{@as(i32, 2)}, i32, .{}));
721-
testing.expectEqual(@as(i32, 3), try inst.invoke("fib", .{@as(i32, 3)}, i32, .{}));
722-
testing.expectEqual(@as(i32, 5), try inst.invoke("fib", .{@as(i32, 4)}, i32, .{}));
723-
testing.expectEqual(@as(i32, 8), try inst.invoke("fib", .{@as(i32, 5)}, i32, .{}));
724-
testing.expectEqual(@as(i32, 13), try inst.invoke("fib", .{@as(i32, 6)}, i32, .{}));
720+
var in = [1]u64{0};
721+
var out = [1]u64{0};
722+
try inst.invoke("fib", in[0..], out[0..], .{});
723+
testing.expectEqual(@as(i32, 1), @bitCast(i32, @truncate(u32, out[0])));
724+
725+
in[0] = 1;
726+
try inst.invoke("fib", in[0..], out[0..], .{});
727+
testing.expectEqual(@as(i32, 1), @bitCast(i32, @truncate(u32, out[0])));
728+
729+
in[0] = 2;
730+
try inst.invoke("fib", in[0..], out[0..], .{});
731+
testing.expectEqual(@as(i32, 2), @bitCast(i32, @truncate(u32, out[0])));
732+
733+
in[0] = 3;
734+
try inst.invoke("fib", in[0..], out[0..], .{});
735+
testing.expectEqual(@as(i32, 3), @bitCast(i32, @truncate(u32, out[0])));
736+
737+
in[0] = 4;
738+
try inst.invoke("fib", in[0..], out[0..], .{});
739+
testing.expectEqual(@as(i32, 5), @bitCast(i32, @truncate(u32, out[0])));
740+
741+
in[0] = 5;
742+
try inst.invoke("fib", in[0..], out[0..], .{});
743+
testing.expectEqual(@as(i32, 8), @bitCast(i32, @truncate(u32, out[0])));
744+
745+
in[0] = 6;
746+
try inst.invoke("fib", in[0..], out[0..], .{});
747+
testing.expectEqual(@as(i32, 13), @bitCast(i32, @truncate(u32, out[0])));
725748
}
726749

727750
test "module loading (fact)" {
@@ -739,9 +762,24 @@ test "module loading (fact)" {
739762
var inst = Instance.init(&arena.allocator, &store, module);
740763
try inst.instantiate();
741764

742-
testing.expectEqual(@as(i32, 1), try inst.invoke("fact", .{@as(i32, 1)}, i32, .{}));
743-
testing.expectEqual(@as(i32, 2), try inst.invoke("fact", .{@as(i32, 2)}, i32, .{}));
744-
testing.expectEqual(@as(i32, 6), try inst.invoke("fact", .{@as(i32, 3)}, i32, .{}));
745-
testing.expectEqual(@as(i32, 24), try inst.invoke("fact", .{@as(i32, 4)}, i32, .{}));
746-
testing.expectEqual(@as(i32, 479001600), try inst.invoke("fact", .{@as(i32, 12)}, i32, .{}));
765+
var in = [1]u64{1};
766+
var out = [1]u64{0};
767+
try inst.invoke("fact", in[0..], out[0..], .{});
768+
testing.expectEqual(@as(i32, 1), @bitCast(i32, @truncate(u32, out[0])));
769+
770+
in[0] = 2;
771+
try inst.invoke("fact", in[0..], out[0..], .{});
772+
testing.expectEqual(@as(i32, 2), @bitCast(i32, @truncate(u32, out[0])));
773+
774+
in[0] = 3;
775+
try inst.invoke("fact", in[0..], out[0..], .{});
776+
testing.expectEqual(@as(i32, 6), @bitCast(i32, @truncate(u32, out[0])));
777+
778+
in[0] = 4;
779+
try inst.invoke("fact", in[0..], out[0..], .{});
780+
testing.expectEqual(@as(i32, 24), @bitCast(i32, @truncate(u32, out[0])));
781+
782+
in[0] = 12;
783+
try inst.invoke("fact", in[0..], out[0..], .{});
784+
testing.expectEqual(@as(i32, 479001600), @bitCast(i32, @truncate(u32, out[0])));
747785
}

Diff for: test/src/testrunner.zig

+11-22
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,6 @@ pub fn main() anyerror!void {
277277
}
278278
}
279279

280-
if (expected.len > 1) {
281-
std.debug.warn("SKIPPING MULTI-VALUE\n", .{});
282-
continue;
283-
}
284-
285280
// Allocate input parameters and output results
286281
var in = try arena.allocator.alloc(u64, action.invoke.args.len);
287282
var out = try arena.allocator.alloc(u64, expected.len);
@@ -293,13 +288,12 @@ pub fn main() anyerror!void {
293288
}
294289

295290
// Invoke the function
296-
instance.invokeDynamic(field, in, out, .{}) catch |err| {
291+
instance.invoke(field, in, out, .{}) catch |err| {
297292
std.debug.warn("(result) invoke = {s}\n", .{field});
298293
std.debug.warn("Testsuite failure: {s} at {s}:{}\n", .{ field, r.source_filename, command.assert_return.line });
299294
return err;
300295
};
301296

302-
// Test the result
303297
for (expected) |result, i| {
304298
const value_type = try valueTypeFromString(result.@"type");
305299
if (mem.startsWith(u8, result.value, "nan:")) {
@@ -323,7 +317,7 @@ pub fn main() anyerror!void {
323317
std.debug.warn("Testsuite failure: {s} at {s}:{}\n", .{ field, r.source_filename, command.assert_return.line });
324318
std.debug.warn("result[{}], expected: {s} ({x}), result: {} ({x})\n", .{ i, result.value, result_value, out[i], out[i] });
325319
}
326-
if (result_value != out[i]) {
320+
if (result_value != out[expected.len - i - 1]) {
327321
return error.TestsuiteTestFailureTrapResult;
328322
}
329323
}
@@ -400,7 +394,7 @@ pub fn main() anyerror!void {
400394

401395
// Test the result
402396
if (mem.eql(u8, trap, "integer divide by zero")) {
403-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
397+
if (instance.invoke(field, in, out, .{})) |x| {
404398
return error.TestsuiteExpectedTrap;
405399
} else |err| switch (err) {
406400
error.DivisionByZero => continue,
@@ -409,7 +403,7 @@ pub fn main() anyerror!void {
409403
}
410404

411405
if (mem.eql(u8, trap, "integer overflow")) {
412-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
406+
if (instance.invoke(field, in, out, .{})) |x| {
413407
return error.TestsuiteExpectedTrap;
414408
} else |err| switch (err) {
415409
error.Overflow => continue,
@@ -418,7 +412,7 @@ pub fn main() anyerror!void {
418412
}
419413

420414
if (mem.eql(u8, trap, "invalid conversion to integer")) {
421-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
415+
if (instance.invoke(field, in, out, .{})) |x| {
422416
return error.TestsuiteExpectedTrap;
423417
} else |err| switch (err) {
424418
error.InvalidConversion => continue,
@@ -427,7 +421,7 @@ pub fn main() anyerror!void {
427421
}
428422

429423
if (mem.eql(u8, trap, "out of bounds memory access")) {
430-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
424+
if (instance.invoke(field, in, out, .{})) |x| {
431425
return error.TestsuiteExpectedTrap;
432426
} else |err| switch (err) {
433427
error.OutOfBoundsMemoryAccess => continue,
@@ -436,7 +430,7 @@ pub fn main() anyerror!void {
436430
}
437431

438432
if (mem.eql(u8, trap, "indirect call type mismatch")) {
439-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
433+
if (instance.invoke(field, in, out, .{})) |x| {
440434
return error.TestsuiteExpectedTrap;
441435
} else |err| switch (err) {
442436
error.IndirectCallTypeMismatch => continue,
@@ -445,7 +439,7 @@ pub fn main() anyerror!void {
445439
}
446440

447441
if (mem.eql(u8, trap, "undefined element") or mem.eql(u8, trap, "uninitialized element")) {
448-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
442+
if (instance.invoke(field, in, out, .{})) |x| {
449443
return error.TestsuiteExpectedTrap;
450444
} else |err| switch (err) {
451445
error.UndefinedElement => continue,
@@ -458,7 +452,7 @@ pub fn main() anyerror!void {
458452
}
459453

460454
if (mem.eql(u8, trap, "uninitialized") or mem.eql(u8, trap, "undefined") or mem.eql(u8, trap, "indirect call")) {
461-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
455+
if (instance.invoke(field, in, out, .{})) |x| {
462456
return error.TestsuiteExpectedTrap;
463457
} else |err| switch (err) {
464458
error.UndefinedElement => continue,
@@ -472,7 +466,7 @@ pub fn main() anyerror!void {
472466
}
473467

474468
if (mem.eql(u8, trap, "unreachable")) {
475-
if (instance.invokeDynamic(field, in, out, .{})) |x| {
469+
if (instance.invoke(field, in, out, .{})) |x| {
476470
return error.TestsuiteExpectedUnreachable;
477471
} else |err| switch (err) {
478472
error.TrapUnreachable => continue,
@@ -707,11 +701,6 @@ pub fn main() anyerror!void {
707701
const field = action.invoke.field;
708702
std.debug.warn("(return): {s}:{}\n", .{ r.source_filename, command.action.line });
709703

710-
if (expected.len > 1) {
711-
std.debug.warn("SKIPPING MULTI-VALUE\n", .{});
712-
continue;
713-
}
714-
715704
// Allocate input parameters and output results
716705
var in = try arena.allocator.alloc(u64, action.invoke.args.len);
717706
var out = try arena.allocator.alloc(u64, expected.len);
@@ -723,7 +712,7 @@ pub fn main() anyerror!void {
723712
}
724713

725714
// Invoke the function
726-
inst.invokeDynamic(field, in, out, .{}) catch |err| {
715+
inst.invoke(field, in, out, .{}) catch |err| {
727716
std.debug.warn("(result) invoke = {s}\n", .{field});
728717
std.debug.warn("Testsuite failure: {s} at {s}:{}\n", .{ field, r.source_filename, command.action.line });
729718
return err;

0 commit comments

Comments
 (0)