Adding sym_visibility to vm.import and fixing parsing. (#12325)

Previously only vm.import ops with argument names could be parsed
preventing round-tripping of func.func -> vm.import ops.

Progress on unblocking #12157.
diff --git a/compiler/src/iree/compiler/Dialect/HAL/hal.imports.mlir b/compiler/src/iree/compiler/Dialect/HAL/hal.imports.mlir
index 6a425a2..543e178 100644
--- a/compiler/src/iree/compiler/Dialect/HAL/hal.imports.mlir
+++ b/compiler/src/iree/compiler/Dialect/HAL/hal.imports.mlir
@@ -8,10 +8,10 @@
 // Experimental/temporary ops
 //===----------------------------------------------------------------------===//
 
-vm.import @ex.shared_device() -> !vm.ref<!hal.device>
+vm.import private @ex.shared_device() -> !vm.ref<!hal.device>
 attributes {nosideeffects}
 
-vm.import @ex.submit_and_wait(
+vm.import private @ex.submit_and_wait(
   %device : !vm.ref<!hal.device>,
   %command_buffer : !vm.ref<!hal.command_buffer>
 )
@@ -22,7 +22,7 @@
 //===----------------------------------------------------------------------===//
 
 // Allocates a buffer from the allocator.
-vm.import @allocator.allocate(
+vm.import private @allocator.allocate(
   %allocator : !vm.ref<!hal.allocator>,
   %memory_types : i32,
   %buffer_usage : i32,
@@ -31,7 +31,7 @@
 
 // Allocates a buffer from the allocator with an initial value provided by a
 // VM byte buffer.
-vm.import @allocator.allocate.initialized(
+vm.import private @allocator.allocate.initialized(
   %allocator : !vm.ref<!hal.allocator>,
   %memory_types : i32,
   %buffer_usage : i32,
@@ -43,7 +43,7 @@
 // Maps a host byte buffer into a device buffer.
 // If try!=0 then returns null if the given memory type cannot be mapped.
 // Host-local+constant requests will always succeed.
-vm.import @allocator.map.byte_buffer(
+vm.import private @allocator.map.byte_buffer(
   %allocator : !vm.ref<!hal.allocator>,
   %try : i32,
   %memory_types : i32,
@@ -58,7 +58,7 @@
 //===----------------------------------------------------------------------===//
 
 // Returns the allocator the buffer was allocated with.
-vm.import @buffer.assert(
+vm.import private @buffer.assert(
   %buffer : !vm.ref<!hal.buffer>,
   %message : !vm.buffer,
   %allocator : !vm.ref<!hal.allocator>,
@@ -68,7 +68,7 @@
 )
 
 // Returns a reference to a subspan of the buffer.
-vm.import @buffer.subspan(
+vm.import private @buffer.subspan(
   %source_buffer : !vm.ref<!hal.buffer>,
   %source_offset : i64,
   %length : i64
@@ -76,7 +76,7 @@
 attributes {nosideeffects}
 
 // Returns the byte length of the buffer (may be less than total allocation).
-vm.import @buffer.length(
+vm.import private @buffer.length(
   %buffer : !vm.ref<!hal.buffer>
 ) -> i64
 attributes {nosideeffects}
@@ -87,14 +87,14 @@
 // an invalidation mechanism.
 
 // Loads a value from a buffer by mapping it.
-vm.import @buffer.load(
+vm.import private @buffer.load(
   %source_buffer : !vm.ref<!hal.buffer>,
   %source_offset : i64,
   %length : i32
 ) -> i32
 
 // Stores a value into a buffer by mapping it.
-vm.import @buffer.store(
+vm.import private @buffer.store(
   %value : i32,
   %target_buffer : !vm.ref<!hal.buffer>,
   %target_offset : i64,
@@ -106,7 +106,7 @@
 //===----------------------------------------------------------------------===//
 
 // Creates a reference to a buffer with a particular shape and element type.
-vm.import @buffer_view.create(
+vm.import private @buffer_view.create(
   %buffer : !vm.ref<!hal.buffer>,
   %source_offset : i64,
   %source_length : i64,
@@ -117,7 +117,7 @@
 attributes {nosideeffects}
 
 // Asserts a buffer view matches the given tensor encoding and shape.
-vm.import @buffer_view.assert(
+vm.import private @buffer_view.assert(
   %buffer_view : !vm.ref<!hal.buffer_view>,
   %message : !vm.buffer,
   %element_type : i32,
@@ -126,38 +126,38 @@
 )
 
 // Returns the backing buffer of the buffer view.
-vm.import @buffer_view.buffer(
+vm.import private @buffer_view.buffer(
   %buffer_view : !vm.ref<!hal.buffer_view>
 ) -> !vm.ref<!hal.buffer>
 attributes {nosideeffects}
 
 // Returns the element type of the buffer view.
-vm.import @buffer_view.element_type(
+vm.import private @buffer_view.element_type(
   %buffer_view : !vm.ref<!hal.buffer_view>,
 ) -> i32
 attributes {nosideeffects}
 
 // Returns the encoding type of the buffer view.
-vm.import @buffer_view.encoding_type(
+vm.import private @buffer_view.encoding_type(
   %buffer_view : !vm.ref<!hal.buffer_view>,
 ) -> i32
 attributes {nosideeffects}
 
 // Returns the rank of the buffer view.
-vm.import @buffer_view.rank(
+vm.import private @buffer_view.rank(
   %buffer_view : !vm.ref<!hal.buffer_view>,
 ) -> i32
 attributes {nosideeffects}
 
 // Returns the value of the given dimension.
-vm.import @buffer_view.dim(
+vm.import private @buffer_view.dim(
   %buffer_view : !vm.ref<!hal.buffer_view>,
   %index : i32
 ) -> i64
 attributes {nosideeffects}
 
 // Prints out the content of buffer views.
-vm.import @buffer_view.trace(
+vm.import private @buffer_view.trace(
   %key : !vm.buffer,
   %operands : !vm.ref<!hal.buffer_view> ...
 )
@@ -167,7 +167,7 @@
 //===----------------------------------------------------------------------===//
 
 // Creates a new channel for collective communication.
-vm.import @channel.create(
+vm.import private @channel.create(
   %device : !vm.ref<!hal.device>,
   %queue_affinity : i64,
   %rank : i32,
@@ -176,7 +176,7 @@
 attributes {nosideeffects}
 
 // Returns the rank of the local participant in the group and the group count.
-vm.import @channel.rank_and_count(
+vm.import private @channel.rank_and_count(
   %channel : !vm.ref<!hal.channel>
 ) -> (i32, i32)
 attributes {nosideeffects}
@@ -186,7 +186,7 @@
 //===----------------------------------------------------------------------===//
 
 // Returns a command buffer from the device pool ready to begin recording.
-vm.import @command_buffer.create(
+vm.import private @command_buffer.create(
   %device : !vm.ref<!hal.device>,
   %modes : i32,
   %command_categories : i32,
@@ -195,25 +195,25 @@
 
 // Finalizes recording into the command buffer and prepares it for submission.
 // No more commands can be recorded afterward.
-vm.import @command_buffer.finalize(
+vm.import private @command_buffer.finalize(
   %command_buffer : !vm.ref<!hal.command_buffer>
 )
 
 // Pushes a new debug group with the given |label|.
-vm.import @command_buffer.begin_debug_group(
+vm.import private @command_buffer.begin_debug_group(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %label : !vm.buffer
 )
 
 // Pops a debug group from the stack.
-vm.import @command_buffer.end_debug_group(
+vm.import private @command_buffer.end_debug_group(
   %command_buffer : !vm.ref<!hal.command_buffer>
 )
 
 // Defines an execution dependency between all commands recorded before the
 // barrier and all commands recorded after the barrier. Only the stages provided
 // will be affected.
-vm.import @command_buffer.execution_barrier(
+vm.import private @command_buffer.execution_barrier(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %source_stage_mask : i32,
   %target_stage_mask : i32,
@@ -221,7 +221,7 @@
 )
 
 // Fills the target buffer with the given repeating value.
-vm.import @command_buffer.fill_buffer(
+vm.import private @command_buffer.fill_buffer(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %target_buffer : !vm.ref<!hal.buffer>,
   %target_offset : i64,
@@ -231,7 +231,7 @@
 )
 
 // Copies a range of one buffer to another.
-vm.import @command_buffer.copy_buffer(
+vm.import private @command_buffer.copy_buffer(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %source_buffer : !vm.ref<!hal.buffer>,
   %source_offset : i64,
@@ -242,7 +242,7 @@
 
 // Dispatches a collective operation defined by |op| using the given buffers.
 // NOTE: order slightly differs from op in order to get better arg alignment.
-vm.import @command_buffer.collective(
+vm.import private @command_buffer.collective(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %channel : !vm.ref<!hal.channel>,
   %op : i32,
@@ -257,7 +257,7 @@
 )
 
 // Pushes constants for consumption by dispatches.
-vm.import @command_buffer.push_constants(
+vm.import private @command_buffer.push_constants(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %pipeline_layout : !vm.ref<!hal.pipeline_layout>,
   %offset : i32,
@@ -265,7 +265,7 @@
 )
 
 // Pushes a descriptor set to the given set number.
-vm.import @command_buffer.push_descriptor_set(
+vm.import private @command_buffer.push_descriptor_set(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %pipeline_layout : !vm.ref<!hal.pipeline_layout>,
   %set : i32,
@@ -274,7 +274,7 @@
 )
 
 // Dispatches an execution request.
-vm.import @command_buffer.dispatch(
+vm.import private @command_buffer.dispatch(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %executable : !vm.ref<!hal.executable>,
   %entry_point : i32,
@@ -285,7 +285,7 @@
 
 // Dispatches an execution request with the dispatch parameters loaded from the
 // given buffer.
-vm.import @command_buffer.dispatch.indirect(
+vm.import private @command_buffer.dispatch.indirect(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %executable : !vm.ref<!hal.executable>,
   %entry_point : i32,
@@ -294,7 +294,7 @@
 )
 
 // Executes a secondary command buffer with the given binding table.
-vm.import @command_buffer.execute.commands(
+vm.import private @command_buffer.execute.commands(
   %command_buffer : !vm.ref<!hal.command_buffer>,
   %commands : !vm.ref<!hal.command_buffer>,
   // <buffer, offset, length>
@@ -306,7 +306,7 @@
 //===----------------------------------------------------------------------===//
 
 // Creates a descriptor set layout that defines the bindings used within a set.
-vm.import @descriptor_set_layout.create(
+vm.import private @descriptor_set_layout.create(
   %device : !vm.ref<!hal.device>,
   %flags : i32,
   // <binding, type, flags>
@@ -320,13 +320,13 @@
 
 // Returns the allocator that can be used to allocate buffers compatible with
 // the device.
-vm.import @device.allocator(
+vm.import private @device.allocator(
   %device : !vm.ref<!hal.device>
 ) -> !vm.ref<!hal.allocator>
 attributes {nosideeffects}
 
 // Returns a tuple of (ok, value) for the given configuration key.
-vm.import @device.query.i64(
+vm.import private @device.query.i64(
   %device : !vm.ref<!hal.device>,
   %category : !vm.buffer,
   %key : !vm.buffer
@@ -336,7 +336,7 @@
 // Returns a queue-ordered transient buffer that will be available for use when
 // the signal fence is reached. The allocation will not be made until the
 // wait fence has been reached.
-vm.import @device.queue.alloca(
+vm.import private @device.queue.alloca(
   %device : !vm.ref<!hal.device>,
   %queue_affinity : i64,
   %wait_fence : !vm.ref<!hal.fence>,
@@ -350,7 +350,7 @@
 // Deallocates a queue-ordered transient buffer.
 // The deallocation will not be made until the wait fence has been reached and
 // once the storage is available for reuse the signal fence will be signaled.
-vm.import @device.queue.dealloca(
+vm.import private @device.queue.dealloca(
   %device : !vm.ref<!hal.device>,
   %queue_affinity : i64,
   %wait_fence : !vm.ref<!hal.fence>,
@@ -362,7 +362,7 @@
 // The command buffers are executed in order as if they were recorded as one.
 // No commands will execute until the wait fence has been reached and the signal
 // fence will be signaled when all commands have completed.
-vm.import @device.queue.execute(
+vm.import private @device.queue.execute(
   %device : !vm.ref<!hal.device>,
   %queue_affinity : i64,
   %wait_fence : !vm.ref<!hal.fence>,
@@ -373,7 +373,7 @@
 // Flushes any locally-pending submissions in the queue.
 // When submitting many queue operations this can be used to eagerly flush
 // earlier submissions while later ones are still being constructed.
-vm.import @device.queue.flush(
+vm.import private @device.queue.flush(
   %device : !vm.ref<!hal.device>,
   %queue_affinity : i64
 )
@@ -383,7 +383,7 @@
 //===----------------------------------------------------------------------===//
 
 // Creates an executable for use with the specified device.
-vm.import @executable.create(
+vm.import private @executable.create(
   %device : !vm.ref<!hal.device>,
   %executable_format : !vm.buffer,
   %executable_data : !vm.buffer,
@@ -397,13 +397,13 @@
 //===----------------------------------------------------------------------===//
 
 // Returns an unsignaled fence that defines a point in time.
-vm.import @fence.create(
+vm.import private @fence.create(
   %device : !vm.ref<!hal.device>,
   %flags : i32
 ) -> !vm.ref<!hal.fence>
 
 // Returns a fence that joins the input fences as a wait-all operation.
-vm.import @fence.join(
+vm.import private @fence.join(
   %fences : !vm.ref<!hal.fence> ...
 ) -> !vm.ref<!hal.fence>
 attributes {nosideeffects}
@@ -411,24 +411,24 @@
 // Queries whether the fence has been reached and returns its status.
 // Returns OK if the fence has been signaled successfully, DEFERRED if it is
 // unsignaled, and otherwise an error indicating the failure.
-vm.import @fence.query(
+vm.import private @fence.query(
   %fence : !vm.ref<!hal.fence>
 ) -> i32
 
 // Signals the fence.
-vm.import @fence.signal(
+vm.import private @fence.signal(
   %fence : !vm.ref<!hal.fence>
 )
 
 // Signals the fence with a failure. The |status| will be returned from
 // `hal.fence.query` and `hal.fence.await`.
-vm.import @fence.fail(
+vm.import private @fence.fail(
   %fence : !vm.ref<!hal.fence>,
   %status : i32
 )
 
 // Yields the caller until all fences is reached.
-vm.import @fence.await(
+vm.import private @fence.await(
   %timeout_millis : i32,
   %fences : !vm.ref<!hal.fence> ...
 ) -> i32
@@ -440,7 +440,7 @@
 
 // Creates an pipeline layout from the given descriptor sets and push constant
 // required size.
-vm.import @pipeline_layout.create(
+vm.import private @pipeline_layout.create(
   %device : !vm.ref<!hal.device>,
   %push_constants : i32,
   %set_layouts : !vm.ref<!hal.descriptor_set_layout>...
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/ImportUtils.cpp b/compiler/src/iree/compiler/Dialect/VM/Conversion/ImportUtils.cpp
index 5c74537..00c8ae2 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/ImportUtils.cpp
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/ImportUtils.cpp
@@ -31,15 +31,14 @@
   importModuleOp.walk([&](IREE::VM::ImportOp importOp) {
     std::string fullName =
         (importModuleOp.getName() + "." + importOp.getName()).str();
-    auto *existingOp = symbolTable.lookup(fullName);
-    // TODO(benvanik): verify that the imports match.
-    if (!existingOp) {
-      auto clonedOp = cast<IREE::VM::ImportOp>(targetBuilder.clone(*importOp));
-      mlir::StringAttr fullNameAttr =
-          mlir::StringAttr::get(clonedOp.getContext(), fullName);
-      clonedOp.setName(fullNameAttr);
-      clonedOp.setPrivate();
+    if (auto *existingOp = symbolTable.lookup(fullName)) {
+      existingOp->erase();
     }
+    auto clonedOp = cast<IREE::VM::ImportOp>(targetBuilder.clone(*importOp));
+    mlir::StringAttr fullNameAttr =
+        mlir::StringAttr::get(clonedOp.getContext(), fullName);
+    clonedOp.setName(fullNameAttr);
+    clonedOp.setPrivate();
   });
   return success();
 }
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVM.cpp b/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVM.cpp
index d9f5cc0..a6b46b3 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVM.cpp
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/ConvertStandardToVM.cpp
@@ -213,6 +213,7 @@
     // Note that attributes are dropped. Consider preserving some if needed.
     auto importOp = rewriter.create<IREE::VM::ImportOp>(
         srcOp.getLoc(), srcOp.getName(), newSignature);
+    importOp.setSymVisibilityAttr(srcOp.getSymVisibilityAttr());
 
     // If there is a fallback then the import is optional.
     if (srcOp->hasAttr("vm.fallback")) {
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/test/func_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/test/func_ops.mlir
index 21b5d8d..52ec24e 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/test/func_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/StandardToVM/test/func_ops.mlir
@@ -30,7 +30,7 @@
 // CHECK-LABEL: @t003_extern_func
 module @t003_extern_func {
 module {
-  // CHECK: vm.import @some.import(i32) -> !vm.buffer
+  // CHECK: vm.import private @some.import(i32) -> !vm.buffer
   // CHECK-SAME: attributes {minimum_version = 4 : i32}
   func.func private @some.import(%arg0: index) -> !util.buffer attributes {
     vm.version = 4 : i32
@@ -56,7 +56,7 @@
     // CHECK: return %[[RET]]
     return %0 : !util.buffer
   }
-  // CHECK: vm.import @some.import(i32) -> !vm.buffer
+  // CHECK: vm.import private @some.import(i32) -> !vm.buffer
   func.func private @some.import(%arg0: index) -> !util.buffer
 }
 }
@@ -65,7 +65,7 @@
 // CHECK-LABEL: @t004_extern_func_signature
 module @t004_extern_func_signature {
 module {
-  // CHECK: vm.import @some.import(i64) -> i64
+  // CHECK: vm.import private @some.import(i64) -> i64
   func.func private @some.import(%arg0: index) -> index attributes {
     vm.signature = (i64) -> i64
   }
@@ -94,7 +94,7 @@
     // CHECK: return %[[RET]] : i32
     return %0 : index
   }
-  // CHECK: vm.import @some.import(i64) -> i64
+  // CHECK: vm.import private @some.import(i64) -> i64
   func.func private @some.import(%arg0: index) -> index attributes {
     vm.signature = (i64) -> i64
   }
@@ -136,7 +136,7 @@
 module @t006_external_call_fallback {
 module {
   // NOTE: we require conversion for the import but not the fallback!
-  // CHECK: vm.import optional @some.import(i64) -> i64
+  // CHECK: vm.import private optional @some.import(i64) -> i64
   func.func private @some.import(%arg0: index) -> index attributes {
     vm.signature = (i64) -> i64,
     vm.fallback = @some_fallback
@@ -173,12 +173,12 @@
 module @t007_external_call_fallback_import {
 module {
   // NOTE: we require conversion for the import but not the fallback!
-  // CHECK: vm.import optional @some.import(i64) -> i64
+  // CHECK: vm.import private optional @some.import(i64) -> i64
   func.func private @some.import(%arg0: index) -> index attributes {
     vm.signature = (i64) -> i64,
     vm.fallback = @other.fallback
   }
-  // CHECK: vm.import @other.fallback(i64) -> i64
+  // CHECK: vm.import private @other.fallback(i64) -> i64
   func.func private @other.fallback(%arg0: index) -> index attributes {
     vm.signature = (i64) -> i64
   }
@@ -211,7 +211,7 @@
 // CHECK-LABEL: @t007_extern_func_opaque_types
 module @t007_extern_func_opaque_types {
 module {
-  // CHECK: vm.import @some.import() -> !vm.ref<!some.type<foo>>
+  // CHECK: vm.import private @some.import() -> !vm.ref<!some.type<foo>>
   func.func private @some.import() -> !some.type<foo>
   // CHECK: vm.func private @my_fn() -> !vm.ref<!some.type<foo>>
   func.func @my_fn() -> !some.type<foo> {
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/UtilToVM/test/global_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/Conversion/UtilToVM/test/global_ops.mlir
index 399b4d5..a0825d0 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/UtilToVM/test/global_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/UtilToVM/test/global_ops.mlir
@@ -20,7 +20,7 @@
   util.global.store %0, @v_initialized : !hal.buffer
   util.initializer.return
 }
-// CHECK-NEXT: vm.import @initializer() -> !vm.ref<!hal.buffer>
+// CHECK-NEXT: vm.import private @initializer() -> !vm.ref<!hal.buffer>
 func.func private @initializer() -> !hal.buffer
 
 // -----
diff --git a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
index d66f1b6..a7c8e8a 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Conversion/VMToEmitC/test/control_flow_ops.mlir
@@ -61,7 +61,7 @@
 // Test vm.call conversion on an imported function.
 vm.module @my_module {
   // CHECK: func.func @my_module_call_[[IMPORTFN:[^\(]+]]
-  vm.import @imported_fn(%arg0 : i32) -> i32
+  vm.import private @imported_fn(%arg0 : i32) -> i32
 
   // CHECK: func.func @my_module_call_imported_fn
   vm.func @call_imported_fn(%arg0 : i32) -> i32 {
@@ -111,7 +111,7 @@
   }
 
   // CHECK: func.func @my_module_call_[[IMPORTFN]]
-  vm.import @imported_fn(%arg0 : i32) -> i32
+  vm.import private @imported_fn(%arg0 : i32) -> i32
 }
 
 // -----
@@ -144,7 +144,7 @@
 // Test vm.call.variadic conversion on an imported function.
 vm.module @my_module {
   // CHECK: func.func @my_module_call_[[VARIADICFN:[^\(]+]]
-  vm.import @variadic_fn(%arg0 : i32 ...) -> i32
+  vm.import private @variadic_fn(%arg0 : i32 ...) -> i32
 
   // CHECK: func.func @my_module_call_variadic
   vm.func @call_variadic(%arg0 : i32, %arg1 : i32) -> i32 {
@@ -177,7 +177,7 @@
 // Test vm.call.variadic with zero arguments.
 vm.module @my_module {
   // CHECK: func.func @my_module_call_[[VARIADICFN:[^\(]+]]
-  vm.import @variadic_fn(%arg0 : i32 ...) -> i32
+  vm.import private @variadic_fn(%arg0 : i32 ...) -> i32
 
   // CHECK: func.func @my_module_call_variadic
   vm.func @call_variadic() -> i32 {
@@ -211,7 +211,7 @@
 // Test vm.call.variadic with multiple variadic packs.
 vm.module @my_module {
   // CHECK: func.func @my_module_call_[[VARIADICFN:[^\(]+]]
-  vm.import @variadic_fn(%is : i32 ..., %fs : f32 ...) -> i32
+  vm.import private @variadic_fn(%is : i32 ..., %fs : f32 ...) -> i32
 
   // CHECK: func.func @my_module_call_variadic
   vm.func @call_variadic(%i : i32, %f : f32) -> i32 {
@@ -322,7 +322,7 @@
 
   // Calculate the size of the result. To avoid empty structs we insert a dummy value.
   // CHECK-NEXT: %[[RESULTSIZE:.+]] = "emitc.constant"() {value = #emitc.opaque<"0">} : () -> !emitc.opaque<"iree_host_size_t">
-  
+
   // Create a struct for the arguments and results.
   // CHECK: %[[ARGSTRUCT:.+]] = "emitc.constant"() {value = #emitc.opaque<"">} : () -> !emitc.opaque<"iree_vm_function_call_t">
   // CHECK-NEXT: %[[ARGSTRUCTFN:.+]] = emitc.apply "*"(%arg1) : (!emitc.ptr<!emitc.opaque<"iree_vm_function_t">>) -> !emitc.opaque<"iree_vm_function_t">
@@ -363,7 +363,7 @@
   // Return ok status.
   //      CHECK: %[[OK:.+]] = emitc.call "iree_ok_status"()
   // CHECK-NEXT: return %[[OK]]
-  vm.import @ref_fn() -> ()
+  vm.import private @ref_fn() -> ()
 
   vm.func @import_ref() -> () {
     vm.call @ref_fn() : () -> ()
@@ -466,7 +466,7 @@
   // Return ok status.
   // CHECK-NEXT: %[[OK:.+]] = emitc.call "iree_ok_status"()
   // CHECK-NEXT: return %[[OK]]
-  vm.import @variadic_fn(%arg0 : i32, %arg1 : i32 ...) -> i32
+  vm.import private @variadic_fn(%arg0 : i32, %arg1 : i32 ...) -> i32
 
   vm.func @import_variadic(%arg0 : i32, %arg1 : i32, %arg2 : i32) -> i32 {
     %0 = vm.call.variadic @variadic_fn(%arg0, [%arg1, %arg2]) : (i32, i32 ...) -> i32
@@ -553,7 +553,7 @@
   // Return ok status.
   // CHECK-NEXT: %[[OK:.+]] = emitc.call "iree_ok_status"()
   // CHECK-NEXT: return %[[OK]]
-  vm.import @variadic_fn(%arg0 : i32, %arg1 : i32 ...) -> i32
+  vm.import private @variadic_fn(%arg0 : i32, %arg1 : i32 ...) -> i32
 
   vm.func @import_variadic(%arg0 : i32) -> i32 {
     %0 = vm.call.variadic @variadic_fn(%arg0, []) : (i32, i32 ...) -> i32
@@ -628,7 +628,7 @@
   // Return ok status.
   // CHECK-NEXT: %[[OK:.+]] = emitc.call "iree_ok_status"()
   // CHECK-NEXT: return %[[OK]]
-  vm.import @ref_fn(%arg0 : !vm.ref<?>) -> !vm.ref<?>
+  vm.import private @ref_fn(%arg0 : !vm.ref<?>) -> !vm.ref<?>
 
   vm.func @import_ref(%arg0 : !vm.ref<?>) -> !vm.ref<?> {
     %0 = vm.call @ref_fn(%arg0) : (!vm.ref<?>) -> !vm.ref<?>
@@ -715,7 +715,7 @@
 // -----
 
 vm.module @my_module {
-  vm.import optional @optional_import_fn(%arg0 : i32) -> i32
+  vm.import private optional @optional_import_fn(%arg0 : i32) -> i32
   // CHECK-LABEL: @my_module_call_fn
   vm.func @call_fn() -> i32 {
     // CHECK-NEXT: %[[IMPORTS:.+]] = emitc.call "EMITC_STRUCT_PTR_MEMBER"(%arg2) {args = [0 : index, #emitc.opaque<"imports">]} : (!emitc.ptr<!emitc.opaque<"my_module_state_t">>) -> !emitc.ptr<!emitc.opaque<"iree_vm_function_t">>
diff --git a/compiler/src/iree/compiler/Dialect/VM/IR/VMOps.cpp b/compiler/src/iree/compiler/Dialect/VM/IR/VMOps.cpp
index f2e5a06..5647d62 100644
--- a/compiler/src/iree/compiler/Dialect/VM/IR/VMOps.cpp
+++ b/compiler/src/iree/compiler/Dialect/VM/IR/VMOps.cpp
@@ -235,6 +235,13 @@
 
 ParseResult ImportOp::parse(OpAsmParser &parser, OperationState &result) {
   auto builder = parser.getBuilder();
+  StringAttr visibilityAttr;
+  if (failed(parseSymbolVisibility(parser, visibilityAttr))) {
+    return failure();
+  }
+  if (visibilityAttr) {
+    result.addAttribute("sym_visibility", visibilityAttr);
+  }
   if (succeeded(parser.parseOptionalKeyword("optional"))) {
     result.addAttribute("is_optional", builder.getUnitAttr());
   }
@@ -248,17 +255,25 @@
   SmallVector<DictionaryAttr, 8> argAttrs;
   SmallVector<Type, 8> argTypes;
   while (failed(parser.parseOptionalRParen())) {
-    OpAsmParser::UnresolvedOperand operand;
+    StringRef operandName;
     Type operandType;
     auto operandLoc = parser.getCurrentLocation();
-    if (failed(parser.parseOperand(operand)) ||
-        failed(parser.parseColonType(operandType))) {
-      return parser.emitError(operandLoc) << "invalid operand";
+    OpAsmParser::UnresolvedOperand operand;
+    if (parser.parseOptionalOperand(operand).has_value()) {
+      if (failed(parser.parseColonType(operandType))) {
+        return parser.emitError(operandLoc) << "invalid operand";
+      }
+      operandName = operand.name.substr(1);  // consume `%`
+    } else {
+      if (failed(parser.parseType(operandType))) {
+        return parser.emitError(operandLoc) << "invalid operand";
+      }
     }
     argTypes.push_back(operandType);
     NamedAttrList argAttrList;
-    operand.name.consume_front("%");
-    argAttrList.set("vm.name", builder.getStringAttr(operand.name));
+    if (!operandName.empty()) {
+      argAttrList.set("vm.name", builder.getStringAttr(operandName));
+    }
     if (succeeded(parser.parseOptionalEllipsis())) {
       argAttrList.set("vm.variadic", builder.getUnitAttr());
     }
@@ -297,6 +312,8 @@
 void ImportOp::print(OpAsmPrinter &p) {
   Operation *op = getOperation();
   p << ' ';
+  printSymbolVisibility(p, op, getSymVisibilityAttr());
+  p << ' ';
   if (getIsOptional()) {
     p << "optional ";
   }
@@ -329,6 +346,7 @@
           getResAttrsAttrName(),
           "is_variadic",
           getIsOptionalAttrName(),
+          "sym_visibility",
       });
 }
 
diff --git a/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_folding.mlir b/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_folding.mlir
index ed2c9b0..79a176f 100644
--- a/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_folding.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_folding.mlir
@@ -63,8 +63,8 @@
     // CHECK-NEXT: vm.return
     vm.return
   }
-  vm.import @nonvariadic_pure_func(%arg0 : i32) -> i32 attributes {nosideeffects}
-  vm.import @variadic_pure_func(%arg0 : i32 ...) -> i32 attributes {nosideeffects}
+  vm.import private @nonvariadic_pure_func(%arg0 : i32) -> i32 attributes {nosideeffects}
+  vm.import private @variadic_pure_func(%arg0 : i32 ...) -> i32 attributes {nosideeffects}
 
   // CHECK-LABEL: @convert_nonvariadic_to_call
   vm.func @convert_nonvariadic_to_call(%arg0 : i32) -> (i32, i32) {
@@ -75,8 +75,8 @@
     // CHECK-NEXT: vm.return
     vm.return %0, %1 : i32, i32
   }
-  vm.import @nonvariadic_func(%arg0 : i32) -> i32
-  vm.import @variadic_func(%arg0 : i32, %arg1 : i32 ...) -> i32
+  vm.import private @nonvariadic_func(%arg0 : i32) -> i32
+  vm.import private @variadic_func(%arg0 : i32, %arg1 : i32 ...) -> i32
 }
 
 // -----
@@ -178,8 +178,8 @@
 
 // CHECK-LABEL: @check_imports
 vm.module @check_imports {
-  vm.import @required_import_fn(%arg0 : i32) -> i32
-  vm.import optional @optional_import_fn(%arg0 : i32) -> i32
+  vm.import private @required_import_fn(%arg0 : i32) -> i32
+  vm.import private optional @optional_import_fn(%arg0 : i32) -> i32
   vm.func @call_fn() -> (i32, i32) {
     // CHECK-NOT: vm.import.resolved @required_import_fn
     // CHECK-DAG: %[[HAS_STRONG:.+]] = vm.const.i32 1
diff --git a/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_ops.mlir
index 9bcfe16..d834099 100644
--- a/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/IR/test/control_flow_ops.mlir
@@ -54,7 +54,7 @@
 
 // CHECK-LABEL: @call_fn
 vm.module @my_module {
-  vm.import @import_fn(%arg0 : i32) -> i32
+  vm.import private @import_fn(%arg0 : i32) -> i32
   vm.func @call_fn(%arg0 : i32) -> i32 {
     // CHECK: %0 = vm.call @import_fn(%arg0) : (i32) -> i32
     %0 = vm.call @import_fn(%arg0) : (i32) -> i32
@@ -66,7 +66,7 @@
 
 // CHECK-LABEL: @call_variadic_but_not_really
 vm.module @my_module {
-  vm.import @import_fn(%arg0 : i32) -> i32
+  vm.import private @import_fn(%arg0 : i32) -> i32
   vm.func @call_variadic_but_not_really(%arg0 : i32) -> i32 {
     // CHECK: %0 = vm.call.variadic @import_fn(%arg0) : (i32) -> i32
     %0 = vm.call.variadic @import_fn(%arg0) : (i32) -> i32
@@ -78,7 +78,7 @@
 
 // CHECK-LABEL: @call_variadic_empty
 vm.module @my_module {
-  vm.import @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...) -> i32
+  vm.import private @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...) -> i32
   vm.func @call_variadic_empty(%arg0 : i32) -> i32 {
     // CHECK: %0 = vm.call.variadic @import_fn(%arg0, []) : (i32, !vm.ref<!hal.buffer> ...) -> i32
     %0 = vm.call.variadic @import_fn(%arg0, []) : (i32, !vm.ref<!hal.buffer> ...) -> i32
@@ -90,7 +90,7 @@
 
 // CHECK-LABEL: @call_variadic
 vm.module @my_module {
-  vm.import @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...) -> i32
+  vm.import private @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...) -> i32
   vm.func @call_variadic(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer>) -> i32 {
     // CHECK: %0 = vm.call.variadic @import_fn(%arg0, [%arg1, %arg1]) : (i32, !vm.ref<!hal.buffer> ...) -> i32
     %0 = vm.call.variadic @import_fn(%arg0, [%arg1, %arg1]) : (i32, !vm.ref<!hal.buffer> ...) -> i32
@@ -102,7 +102,7 @@
 
 // CHECK-LABEL: @call_variadic_multiple
 vm.module @my_module {
-  vm.import @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...) -> i32
+  vm.import private @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...) -> i32
   vm.func @call_variadic_multiple(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer>) -> i32 {
     // CHECK: %0 = vm.call.variadic @import_fn(%arg0, [%arg1, %arg1], [%arg1]) : (i32, !vm.ref<!hal.buffer> ..., !vm.ref<!hal.buffer> ...) -> i32
     %0 = vm.call.variadic @import_fn(%arg0, [%arg1, %arg1], [%arg1]) : (i32, !vm.ref<!hal.buffer> ..., !vm.ref<!hal.buffer> ...) -> i32
@@ -114,7 +114,7 @@
 
 // CHECK-LABEL: @call_variadic_no_results
 vm.module @my_module {
-  vm.import @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...)
+  vm.import private @import_fn(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer> ...)
   vm.func @call_variadic_no_results(%arg0 : i32, %arg1 : !vm.ref<!hal.buffer>) {
     // CHECK: vm.call.variadic @import_fn(%arg0, [%arg1, %arg1], [%arg1]) : (i32, !vm.ref<!hal.buffer> ..., !vm.ref<!hal.buffer> ...)
     vm.call.variadic @import_fn(%arg0, [%arg1, %arg1], [%arg1]) : (i32, !vm.ref<!hal.buffer> ..., !vm.ref<!hal.buffer> ...)
@@ -126,7 +126,7 @@
 
 // CHECK-LABEL: @call_variadic_tuples
 vm.module @my_module {
-  vm.import @import_fn(%arg0 : tuple<i32, i32, i32> ...)
+  vm.import private @import_fn(%arg0 : tuple<i32, i32, i32> ...)
   vm.func @call_variadic_tuples(%arg0 : i32, %arg1 : i32) {
     // CHECK: vm.call.variadic @import_fn([(%arg0, %arg0, %arg0), (%arg1, %arg1, %arg1)]) : (tuple<i32, i32, i32> ...)
     vm.call.variadic @import_fn([(%arg0, %arg0, %arg0), (%arg1, %arg1, %arg1)]) : (tuple<i32, i32, i32> ...)
@@ -227,8 +227,8 @@
 // -----
 
 vm.module @my_module {
-  // CHECK: vm.import optional @optional_import_fn(%arg0 : i32) -> i32
-  vm.import optional @optional_import_fn(%arg0 : i32) -> i32
+  // CHECK: vm.import private optional @optional_import_fn(%arg0 : i32) -> i32
+  vm.import private optional @optional_import_fn(%arg0 : i32) -> i32
   vm.func @call_fn() -> i32 {
     // CHECK: %has_optional_import_fn = vm.import.resolved @optional_import_fn : i32
     %has_optional_import_fn = vm.import.resolved @optional_import_fn : i32
diff --git a/compiler/src/iree/compiler/Dialect/VM/IR/test/structural_ops.mlir b/compiler/src/iree/compiler/Dialect/VM/IR/test/structural_ops.mlir
index ba22055..eedf24a 100644
--- a/compiler/src/iree/compiler/Dialect/VM/IR/test/structural_ops.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/IR/test/structural_ops.mlir
@@ -63,17 +63,20 @@
 
 // CHECK-LABEL: @import_funcs
 vm.module @import_funcs {
-  // CHECK-NEXT: vm.import @my.fn_empty()
-  vm.import @my.fn_empty()
+  // CHECK-NEXT: vm.import private @my.fn_empty()
+  vm.import private @my.fn_empty()
 
-  // CHECK-NEXT: vm.import @my.fn(%foo : i32, %bar : i32) -> i32
-  vm.import @my.fn(%foo : i32, %bar : i32) -> i32
+  // CHECK-NEXT: vm.import public @my.fn(i32, i32) -> i32
+  vm.import public @my.fn(i32, i32) -> i32
 
-  // CHECK-NEXT: vm.import @my.fn_attrs(%foo : i32, %bar : i32) -> i32 attributes {a}
-  vm.import @my.fn_attrs(%foo : i32, %bar : i32) -> i32 attributes {a}
+  // CHECK-NEXT: vm.import private @my.fn_names(%foo : i32, %bar : i32) -> i32
+  vm.import private @my.fn_names(%foo : i32, %bar : i32) -> i32
 
-  // CHECK-NEXT: vm.import @my.fn_varargs(%foo : vector<3xi32> ..., %bar : tuple<i32, i32> ...) -> i32
-  vm.import @my.fn_varargs(%foo : vector<3xi32> ..., %bar : tuple<i32, i32> ...) -> i32
+  // CHECK-NEXT: vm.import private @my.fn_attrs(%foo : i32, %bar : i32) -> i32 attributes {a}
+  vm.import private @my.fn_attrs(%foo : i32, %bar : i32) -> i32 attributes {a}
+
+  // CHECK-NEXT: vm.import private @my.fn_varargs(%foo : vector<3xi32> ..., %bar : tuple<i32, i32> ...) -> i32
+  vm.import private @my.fn_varargs(%foo : vector<3xi32> ..., %bar : tuple<i32, i32> ...) -> i32
 }
 
 // -----
diff --git a/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/test/dependencies.mlir b/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/test/dependencies.mlir
index 28de8dc..b02fd61 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/test/dependencies.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/test/dependencies.mlir
@@ -15,18 +15,18 @@
   // CHECK: "imported_functions":
 
   // CHECK: "full_name": "required.method0"
-  vm.import @required.method0() attributes { minimum_version = 4 : i32 }
+  vm.import private @required.method0() attributes { minimum_version = 4 : i32 }
   // CHECK: "full_name": "required.method1"
-  vm.import @required.method1() attributes { minimum_version = 5 : i32 }
+  vm.import private @required.method1() attributes { minimum_version = 5 : i32 }
   // CHECK: "full_name": "required.method2"
   // CHECK: "flags": "OPTIONAL"
-  vm.import optional @required.method2() attributes { minimum_version = 6 : i32 }
+  vm.import private optional @required.method2() attributes { minimum_version = 6 : i32 }
 
   // CHECK: "full_name": "optional.method0"
   // CHECK: "flags": "OPTIONAL"
-  vm.import optional @optional.method0() attributes { minimum_version = 10 : i32 }
+  vm.import private optional @optional.method0() attributes { minimum_version = 10 : i32 }
   // CHECK: "full_name": "optional.method1"
   // CHECK: "flags": "OPTIONAL"
-  vm.import optional @optional.method1() attributes { minimum_version = 11 : i32 }
+  vm.import private optional @optional.method1() attributes { minimum_version = 11 : i32 }
 
 }
diff --git a/compiler/src/iree/compiler/Dialect/VM/Target/C/test/dependencies.mlir b/compiler/src/iree/compiler/Dialect/VM/Target/C/test/dependencies.mlir
index 266fb3d..a68fe6f 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Target/C/test/dependencies.mlir
+++ b/compiler/src/iree/compiler/Dialect/VM/Target/C/test/dependencies.mlir
@@ -2,12 +2,12 @@
 // RUN: --output-format=vm-c --iree-vm-c-module-optimize=false %s | FileCheck %s
 
 vm.module @main_module attributes { version = 100 : i32 } {
-  vm.import @required.method0() attributes { minimum_version = 4 : i32 }
-  vm.import @required.method1() attributes { minimum_version = 5 : i32 }
-  vm.import optional @required.method2() attributes { minimum_version = 6 : i32 }
+  vm.import public @required.method0() attributes { minimum_version = 4 : i32 }
+  vm.import public @required.method1() attributes { minimum_version = 5 : i32 }
+  vm.import public optional @required.method2() attributes { minimum_version = 6 : i32 }
 
-  vm.import optional @optional.method0() attributes { minimum_version = 10 : i32 }
-  vm.import optional @optional.method1() attributes { minimum_version = 11 : i32 }
+  vm.import public optional @optional.method0() attributes { minimum_version = 10 : i32 }
+  vm.import public optional @optional.method1() attributes { minimum_version = 11 : i32 }
 }
 
 // CHECK: main_module_dependencies_[]
diff --git a/compiler/src/iree/compiler/Dialect/VMVX/vmvx.imports.mlir b/compiler/src/iree/compiler/Dialect/VMVX/vmvx.imports.mlir
index e4e022a..1ea92cc 100644
--- a/compiler/src/iree/compiler/Dialect/VMVX/vmvx.imports.mlir
+++ b/compiler/src/iree/compiler/Dialect/VMVX/vmvx.imports.mlir
@@ -30,7 +30,7 @@
 // Each is specialized by opcode, rank and type width.
 //===----------------------------------------------------------------------===//
 
-vm.import @add.2d.f32(
+vm.import private @add.2d.f32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -46,7 +46,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @add.2d.i32(
+vm.import private @add.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -62,7 +62,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @and.2d.i32(
+vm.import private @and.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -78,7 +78,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @div.2d.f32(
+vm.import private @div.2d.f32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -94,7 +94,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @divs.2d.i32(
+vm.import private @divs.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -110,7 +110,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @divu.2d.i32(
+vm.import private @divu.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -126,7 +126,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @mul.2d.f32(
+vm.import private @mul.2d.f32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -142,7 +142,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @mul.2d.i32(
+vm.import private @mul.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -158,7 +158,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @or.2d.i32(
+vm.import private @or.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -174,7 +174,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @shl.2d.i32(
+vm.import private @shl.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -190,7 +190,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @shrs.2d.i32(
+vm.import private @shrs.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -206,7 +206,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @shru.2d.i32(
+vm.import private @shru.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -222,7 +222,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @sub.2d.f32(
+vm.import private @sub.2d.f32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -238,7 +238,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @sub.2d.i32(
+vm.import private @sub.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -254,7 +254,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @xor.2d.i32(
+vm.import private @xor.2d.i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_strides : tuple<i64, i64>,
@@ -275,7 +275,7 @@
 // Each is specialized by opcode, rank and type width.
 //===----------------------------------------------------------------------===//
 
-vm.import @abs.2d.f32(
+vm.import private @abs.2d.f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -285,7 +285,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @ceil.2d.f32(
+vm.import private @ceil.2d.f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -295,7 +295,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @ctlz.2d.i32(
+vm.import private @ctlz.2d.i32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -305,7 +305,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @exp.2d.f32(
+vm.import private @exp.2d.f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -315,7 +315,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @floor.2d.f32(
+vm.import private @floor.2d.f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -325,7 +325,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @log.2d.f32(
+vm.import private @log.2d.f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -335,7 +335,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @neg.2d.f32(
+vm.import private @neg.2d.f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -345,7 +345,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @rsqrt.2d.f32(
+vm.import private @rsqrt.2d.f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -360,7 +360,7 @@
 // Variants of copy ops exist for power of two rank and datatype sizes.
 // Current max rank is 2d.
 //==============================================================================
-vm.import @copy.2d.x8(
+vm.import private @copy.2d.x8(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -370,7 +370,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @copy.2d.x16(
+vm.import private @copy.2d.x16(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -380,7 +380,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @copy.2d.x32(
+vm.import private @copy.2d.x32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -390,7 +390,7 @@
   %sizes : tuple<i64, i64>
 )
 
-vm.import @copy.2d.x64(
+vm.import private @copy.2d.x64(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_strides : tuple<i64, i64>,
@@ -404,7 +404,7 @@
 // Strided fill ops
 //==============================================================================
 
-vm.import @fill.2d.x32(
+vm.import private @fill.2d.x32(
   %fill_value : i32,
   %out_buffer : !vm.buffer,
   %out_offset : i64,
@@ -417,7 +417,7 @@
 // matmul ops
 //==============================================================================
 
-vm.import @matmul.f32f32f32(
+vm.import private @matmul.f32f32f32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_row_stride : i64,
@@ -433,7 +433,7 @@
   %flags : i32
 )
 
-vm.import @matmul.i8i8i32(
+vm.import private @matmul.i8i8i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_row_stride : i64,
@@ -453,7 +453,7 @@
 // mmt4d ops
 //==============================================================================
 
-vm.import @mmt4d.f32f32f32(
+vm.import private @mmt4d.f32f32f32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_row_stride : i64,
@@ -472,7 +472,7 @@
   %flags : i32
 )
 
-vm.import @mmt4d.i8i8i32(
+vm.import private @mmt4d.i8i8i32(
   %lhs_buffer : !vm.buffer,
   %lhs_offset : i64,
   %lhs_row_stride : i64,
@@ -495,7 +495,7 @@
 // pack ops
 //==============================================================================
 
-vm.import @pack.f32f32(
+vm.import private @pack.f32f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_stride0 : i64,
@@ -512,7 +512,7 @@
   %flags : i32
 )
 
-vm.import @pack.i8i8(
+vm.import private @pack.i8i8(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_stride0 : i64,
@@ -529,7 +529,7 @@
   %flags : i32
 )
 
-vm.import @pack.i32i32(
+vm.import private @pack.i32i32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_stride0 : i64,
@@ -550,7 +550,7 @@
 // unpack ops
 //==============================================================================
 
-vm.import @unpack.f32f32(
+vm.import private @unpack.f32f32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_stride0 : i64,
@@ -566,7 +566,7 @@
   %flags : i32
 )
 
-vm.import @unpack.i8i8(
+vm.import private @unpack.i8i8(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_stride0 : i64,
@@ -582,7 +582,7 @@
   %flags : i32
 )
 
-vm.import @unpack.i32i32(
+vm.import private @unpack.i32i32(
   %in_buffer : !vm.buffer,
   %in_offset : i64,
   %in_stride0 : i64,
@@ -602,7 +602,7 @@
 // query_tile_size ops
 //==============================================================================
 
-vm.import @query_tile_sizes.2d(
+vm.import private @query_tile_sizes.2d(
   %sizes : tuple<i64, i64>,
   %flags : i32
 ) -> (i64, i64)
diff --git a/compiler/src/iree/compiler/Modules/Check/check.imports.mlir b/compiler/src/iree/compiler/Modules/Check/check.imports.mlir
index ed4f622..67bae93 100644
--- a/compiler/src/iree/compiler/Modules/Check/check.imports.mlir
+++ b/compiler/src/iree/compiler/Modules/Check/check.imports.mlir
@@ -6,24 +6,24 @@
 
 vm.module @check {
 
-vm.import optional @expect_true(
+vm.import private optional @expect_true(
   %operand : i32
 )
 
-vm.import optional @expect_false(
+vm.import private optional @expect_false(
   %operand : i32
 )
 
-vm.import optional @expect_all_true(
+vm.import private optional @expect_all_true(
   %operand : !vm.ref<!hal.buffer_view>,
 )
 
-vm.import optional @expect_eq(
+vm.import private optional @expect_eq(
   %lhs : !vm.ref<!hal.buffer_view>,
   %rhs : !vm.ref<!hal.buffer_view>
 )
 
-vm.import optional @expect_almost_eq(
+vm.import private optional @expect_almost_eq(
   %lhs : !vm.ref<!hal.buffer_view>,
   %rhs : !vm.ref<!hal.buffer_view>
 )
diff --git a/compiler/src/iree/compiler/Modules/HAL/Inline/hal_inline.imports.mlir b/compiler/src/iree/compiler/Modules/HAL/Inline/hal_inline.imports.mlir
index dce15ad..54ecf6c 100644
--- a/compiler/src/iree/compiler/Modules/HAL/Inline/hal_inline.imports.mlir
+++ b/compiler/src/iree/compiler/Modules/HAL/Inline/hal_inline.imports.mlir
@@ -12,14 +12,14 @@
 //===----------------------------------------------------------------------===//
 
 // Allocates an empty buffer.
-vm.import @buffer.allocate(
+vm.import private @buffer.allocate(
   %minimum_alignment : i32,
   %allocation_size : i64
 ) -> (!vm.ref<!hal.buffer>, !vm.buffer)
 attributes {nosideeffects}
 
 // Allocates a buffer with an initial value provided by a VM byte buffer.
-vm.import @buffer.allocate.initialized(
+vm.import private @buffer.allocate.initialized(
   %minimum_alignment : i32,
   %source : !vm.buffer,
   %offset : i64,
@@ -28,7 +28,7 @@
 attributes {nosideeffects}
 
 // Wraps a VM byte buffer in a HAL buffer.
-vm.import @buffer.wrap(
+vm.import private @buffer.wrap(
   %source : !vm.buffer,
   %offset : i64,
   %length : i64
@@ -36,7 +36,7 @@
 attributes {nosideeffects}
 
 // Returns a reference to a subspan of the buffer.
-vm.import @buffer.subspan(
+vm.import private @buffer.subspan(
   %source_buffer : !vm.ref<!hal.buffer>,
   %source_offset : i64,
   %length : i64
@@ -46,14 +46,14 @@
 // TODO(benvanik): make storage return length and remove dedicated length?
 
 // Returns the byte length of the buffer (may be less than total allocation).
-vm.import @buffer.length(
+vm.import private @buffer.length(
   %buffer : !vm.ref<!hal.buffer>
 ) -> i64
 attributes {nosideeffects}
 
 // Returns a mapping to the underlying storage of the buffer sliced to the
 // logical subspan of the HAL buffer.
-vm.import @buffer.storage(
+vm.import private @buffer.storage(
   %buffer : !vm.ref<!hal.buffer>
 ) -> !vm.buffer
 attributes {nosideeffects}
@@ -63,7 +63,7 @@
 //===----------------------------------------------------------------------===//
 
 // Creates a reference to a buffer with a particular shape and element type.
-vm.import @buffer_view.create(
+vm.import private @buffer_view.create(
   %source_buffer : !vm.ref<!hal.buffer>,
   %source_offset : i64,
   %source_length : i64,
@@ -74,7 +74,7 @@
 attributes {nosideeffects}
 
 // Asserts a buffer view matches the given tensor encoding and shape.
-vm.import @buffer_view.assert(
+vm.import private @buffer_view.assert(
   %buffer_view : !vm.ref<!hal.buffer_view>,
   %message : !vm.buffer,
   %element_type : i32,
@@ -83,38 +83,38 @@
 )
 
 // Returns the backing buffer of the buffer view.
-vm.import @buffer_view.buffer(
+vm.import private @buffer_view.buffer(
   %buffer_view : !vm.ref<!hal.buffer_view>
 ) -> !vm.ref<!hal.buffer>
 attributes {nosideeffects}
 
 // Returns the element type of the buffer view.
-vm.import @buffer_view.element_type(
+vm.import private @buffer_view.element_type(
   %buffer_view : !vm.ref<!hal.buffer_view>,
 ) -> i32
 attributes {nosideeffects}
 
 // Returns the encoding type of the buffer view.
-vm.import @buffer_view.encoding_type(
+vm.import private @buffer_view.encoding_type(
   %buffer_view : !vm.ref<!hal.buffer_view>,
 ) -> i32
 attributes {nosideeffects}
 
 // Returns the rank of the buffer view.
-vm.import @buffer_view.rank(
+vm.import private @buffer_view.rank(
   %buffer_view : !vm.ref<!hal.buffer_view>,
 ) -> i32
 attributes {nosideeffects}
 
 // Returns the value of the given dimension.
-vm.import @buffer_view.dim(
+vm.import private @buffer_view.dim(
   %buffer_view : !vm.ref<!hal.buffer_view>,
   %index : i32
 ) -> i64
 attributes {nosideeffects}
 
 // Prints out the content of buffer views.
-vm.import @buffer_view.trace(
+vm.import private @buffer_view.trace(
   %key : !vm.buffer,
   %operands : !vm.ref<!hal.buffer_view> ...
 )
@@ -124,7 +124,7 @@
 //===----------------------------------------------------------------------===//
 
 // Returns a tuple of (ok, value) for the given configuration key.
-vm.import @device.query.i64(
+vm.import private @device.query.i64(
   %category : !vm.buffer,
   %key : !vm.buffer
 ) -> (i32, i64)
diff --git a/compiler/src/iree/compiler/Modules/HAL/Loader/hal_loader.imports.mlir b/compiler/src/iree/compiler/Modules/HAL/Loader/hal_loader.imports.mlir
index fc9c538..d811bc7 100644
--- a/compiler/src/iree/compiler/Modules/HAL/Loader/hal_loader.imports.mlir
+++ b/compiler/src/iree/compiler/Modules/HAL/Loader/hal_loader.imports.mlir
@@ -11,13 +11,13 @@
 //===----------------------------------------------------------------------===//
 
 // Queries whether the given executable format is supported.
-vm.import @executable.query_support(
+vm.import private @executable.query_support(
   %executable_format : !vm.buffer
 ) -> i32
 attributes {nosideeffects}
 
 // Creates and dynamically links an executable library.
-vm.import @executable.load(
+vm.import private @executable.load(
   %executable_format : !vm.buffer,
   %executable_data : !vm.buffer,
   %constants : !vm.buffer
@@ -26,7 +26,7 @@
 
 // Dispatches a grid with the given densely-packed and 0-aligned push constants
 // and bindings.
-vm.import @executable.dispatch(
+vm.import private @executable.dispatch(
   %executable : !vm.ref<!hal.executable>,
   %entry_point : i32,
   %workgroup_x : i32,
diff --git a/runtime/src/iree/vm/bytecode_module_benchmark.cc b/runtime/src/iree/vm/bytecode_module_benchmark.cc
index 04e3716..620a4fb 100644
--- a/runtime/src/iree/vm/bytecode_module_benchmark.cc
+++ b/runtime/src/iree/vm/bytecode_module_benchmark.cc
@@ -20,7 +20,7 @@
 typedef struct native_import_module_t native_import_module_t;
 typedef struct native_import_module_state_t native_import_module_state_t;
 
-// vm.import @native_import_module.add_1(%arg0 : i32) -> i32
+// vm.import private @native_import_module.add_1(%arg0 : i32) -> i32
 static iree_status_t native_import_module_add_1(
     iree_vm_stack_t* stack, iree_vm_native_function_flags_t flags,
     iree_byte_span_t args_storage, iree_byte_span_t rets_storage,
diff --git a/runtime/src/iree/vm/bytecode_module_benchmark.mlir b/runtime/src/iree/vm/bytecode_module_benchmark.mlir
index 1459bd9..10c7fd4 100644
--- a/runtime/src/iree/vm/bytecode_module_benchmark.mlir
+++ b/runtime/src/iree/vm/bytecode_module_benchmark.mlir
@@ -36,7 +36,7 @@
   }
 
   // Measures the cost of a call to an imported function.
-  vm.import @native_import_module.add_1(%arg : i32) -> i32
+  vm.import private @native_import_module.add_1(%arg : i32) -> i32
   vm.export @call_imported_func
   vm.func @call_imported_func(%arg0 : i32) -> i32 {
     %0 = vm.call @native_import_module.add_1(%arg0) : (i32) -> i32
diff --git a/runtime/src/iree/vm/native_module_test.h b/runtime/src/iree/vm/native_module_test.h
index b6c1a16..9168a57 100644
--- a/runtime/src/iree/vm/native_module_test.h
+++ b/runtime/src/iree/vm/native_module_test.h
@@ -77,7 +77,7 @@
 typedef struct module_a_t module_a_t;
 typedef struct module_a_state_t module_a_state_t;
 
-// vm.import @module_a.add_1(%arg0 : i32) -> i32
+// vm.import private @module_a.add_1(%arg0 : i32) -> i32
 static iree_status_t module_a_add_1(iree_vm_stack_t* stack, module_a_t* module,
                                     module_a_state_t* module_state,
                                     int32_t arg0, int32_t* out_ret0) {
@@ -86,7 +86,7 @@
   return iree_ok_status();
 }
 
-// vm.import @module_a.sub_1(%arg0 : i32) -> i32
+// vm.import private @module_a.sub_1(%arg0 : i32) -> i32
 static iree_status_t module_a_sub_1(iree_vm_stack_t* stack, module_a_t* module,
                                     module_a_state_t* module_state,
                                     int32_t arg0, int32_t* out_ret0) {
@@ -211,7 +211,7 @@
 // fetching the args, calling the function as a normal C function, and stashing
 // back the results).
 //
-// vm.import @module_b.entry(%arg0 : i32) -> i32
+// vm.import private @module_b.entry(%arg0 : i32) -> i32
 static iree_status_t module_b_entry(iree_vm_stack_t* stack, module_b_t* module,
                                     module_b_state_t* module_state,
                                     int32_t arg0, int32_t* out_ret0) {
diff --git a/runtime/src/iree/vm/test/control_flow_ops.mlir b/runtime/src/iree/vm/test/control_flow_ops.mlir
index 7a54ea1..3b857be 100644
--- a/runtime/src/iree/vm/test/control_flow_ops.mlir
+++ b/runtime/src/iree/vm/test/control_flow_ops.mlir
@@ -45,7 +45,7 @@
   // vm.import.resolved
   //===--------------------------------------------------------------------===//
 
-  vm.import optional @reserved.optional(%arg0: i32) -> i32
+  vm.import private optional @reserved.optional(%arg0: i32) -> i32
 
   // The optional import should not be found.
   vm.export @test_optional_import_resolved
diff --git a/samples/emitc_modules/import_module_a.mlir b/samples/emitc_modules/import_module_a.mlir
index 9c76ab0..99f6e65 100644
--- a/samples/emitc_modules/import_module_a.mlir
+++ b/samples/emitc_modules/import_module_a.mlir
@@ -1,5 +1,5 @@
 vm.module @module_a {
-  vm.import @module_b.square(%arg : i32) -> i32
+  vm.import private @module_b.square(%arg : i32) -> i32
 
   vm.func @test_call(%arg0: i32) -> i32 {
     %0 = vm.call @module_b.square(%arg0) : (i32) -> i32