ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
35.19k stars 2.57k forks source link

Wasm inline assembly miscompilation. #13165

Open gcoakes opened 2 years ago

gcoakes commented 2 years ago

Zig Version

b316c25cc6f5b1703d7912da16c5c987f4406451

Steps to Reproduce

zig build-lib main.zig -OReleaseSmall -dynamic -target wasm32-freestanding -mcpu=generic+reference_types:

comptime {
    asm (
        \\.functype createElement (externref) -> (externref)
        \\.import_module document, document
        \\.import_name __createElement, createElement
        \\
        \\refs:
        \\    .globl refs
        \\    .tabletype refs, externref, 2
        \\    .import_module glue, glue
        \\    .import_name refs, refs
    );
}

const std = @import("std");

const ExternRef = *anyopaque;

fn readString(str: []const u8) ExternRef {
    return glue.readString(str.ptr, str.len);
}

const glue = struct {
    extern "glue" fn readString(ptr: [*]const u8, len: usize) ExternRef;
};

const document = struct {
    pub fn createElement(name: []const u8) ExternRef {
        const ref = readString(name);
        asm volatile (
            \\local.get %[ref]
            \\local.get %[ref]
            \\table.get refs
            \\call __createElement
            \\table.set refs
            :
            : [ref] "r" (ref),
        );
        return ref;
    }
};

export fn foo() ExternRef {
    return document.createElement("h1");
}

Expected Behavior

Created by modifying the actual behavior to resemble the expected behavior:

(module
  (type (;0;) (func (param i32 i32) (result i32)))
  (type (;1;) (func (result i32)))
  (type (;2;) (func (param externref) (result externref)))
  (import "glue" "readString" (func $readString (type 0)))
  (import "document" "createElement" (func $createElement (type 2)))
  (func $foo (type 1) (result i32)
    (local i32)
    i32.const 1048576
    i32.const 2
    call $readString
    local.set 0
    local.get 0
    local.get 0
    table.get 0
    call $createElement
    table.set 0
    local.get 0)
  (table (;0;) 2 externref)
  (memory (;0;) 17)
  (global $__stack_pointer (mut i32) (i32.const 1048576))
  (export "memory" (memory 0))
  (export "foo" (func $foo))
  (data $.rodata (i32.const 1048576) "h1\00"))

Actual Behavior

Generated via wabt's wasm2wat --no-check main.wasm:

(module
  (type (;0;) (func (param i32 i32) (result i32)))
  (type (;1;) (func (result i32)))
  (import "glue" "readString" (func $readString (type 0)))
  (func $foo (type 1) (result i32)
    (local i32)
    i32.const 1048576
    i32.const 2
    call $readString
    local.set 0
    local.get 0
    local.get 0
    table.get 0
    call $readString
    table.set 0
    local.get 0)
  (table (;0;) 2 externref)
  (memory (;0;) 17)
  (global $__stack_pointer (mut i32) (i32.const 1048576))
  (export "memory" (memory 0))
  (export "foo" (func $foo))
  (data $.rodata (i32.const 1048576) "h1\00"))
gcoakes commented 2 years ago

I realized it wasn't terrible obvious... The miscompilation is the second call $readString in the foo function. It should be call $__createElement and have an import for that function.

gcoakes commented 2 years ago

The .functype in the module level assembly has the wrong symbol in my original code. Correcting it, causes a segfault. Maybe related to #13129.

Either way, it seems to be a miscompilation that it changed the unknown symbol __createElement into a reference to the readString function.

alexrp commented 4 weeks ago

This seems like an LLVM assembler bug? A reduction should probably be filed upstream.