Dawn&Tint: Implement F16 pipeline IO

This CL implement f16 for pipeline IO, i.e. vertex shader input,
interstage variables between vertex and fragment shader, and fragment
shader output (render target). Unit tests and E2E tests for Tint and
Dawn are also implemented.

Bugs: tint:1473, tint:1502
Change-Id: If0d6b2b3171ec8b7e4efc0efd58cc803c6a3d3a8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/111160
Commit-Queue: Zhaoming Jiang <zhaoming.jiang@intel.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index d473d89..b05f1b8 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -157,11 +157,12 @@
 ResultOrError<wgpu::TextureComponentType> TintComponentTypeToTextureComponentType(
     tint::inspector::ComponentType type) {
     switch (type) {
-        case tint::inspector::ComponentType::kFloat:
+        case tint::inspector::ComponentType::kF32:
+        case tint::inspector::ComponentType::kF16:
             return wgpu::TextureComponentType::Float;
-        case tint::inspector::ComponentType::kSInt:
+        case tint::inspector::ComponentType::kI32:
             return wgpu::TextureComponentType::Sint;
-        case tint::inspector::ComponentType::kUInt:
+        case tint::inspector::ComponentType::kU32:
             return wgpu::TextureComponentType::Uint;
         case tint::inspector::ComponentType::kUnknown:
             return DAWN_VALIDATION_ERROR("Attempted to convert 'Unknown' component type from Tint");
@@ -172,11 +173,12 @@
 ResultOrError<VertexFormatBaseType> TintComponentTypeToVertexFormatBaseType(
     tint::inspector::ComponentType type) {
     switch (type) {
-        case tint::inspector::ComponentType::kFloat:
+        case tint::inspector::ComponentType::kF32:
+        case tint::inspector::ComponentType::kF16:
             return VertexFormatBaseType::Float;
-        case tint::inspector::ComponentType::kSInt:
+        case tint::inspector::ComponentType::kI32:
             return VertexFormatBaseType::Sint;
-        case tint::inspector::ComponentType::kUInt:
+        case tint::inspector::ComponentType::kU32:
             return VertexFormatBaseType::Uint;
         case tint::inspector::ComponentType::kUnknown:
             return DAWN_VALIDATION_ERROR("Attempted to convert 'Unknown' component type from Tint");
@@ -213,12 +215,14 @@
 ResultOrError<InterStageComponentType> TintComponentTypeToInterStageComponentType(
     tint::inspector::ComponentType type) {
     switch (type) {
-        case tint::inspector::ComponentType::kFloat:
-            return InterStageComponentType::Float;
-        case tint::inspector::ComponentType::kSInt:
-            return InterStageComponentType::Sint;
-        case tint::inspector::ComponentType::kUInt:
-            return InterStageComponentType::Uint;
+        case tint::inspector::ComponentType::kF32:
+            return InterStageComponentType::F32;
+        case tint::inspector::ComponentType::kI32:
+            return InterStageComponentType::I32;
+        case tint::inspector::ComponentType::kU32:
+            return InterStageComponentType::U32;
+        case tint::inspector::ComponentType::kF16:
+            return InterStageComponentType::F16;
         case tint::inspector::ComponentType::kUnknown:
             return DAWN_VALIDATION_ERROR("Attempted to convert 'Unknown' component type from Tint");
     }
@@ -1042,7 +1046,7 @@
             continue;
         }
 
-        // Uint/sint can't be statically used with a sampler, so they any
+        // Uint/Sint can't be statically used with a sampler, so they any
         // texture bindings reflected must be float or depth textures. If
         // the shader uses a float/depth texture but the bind group layout
         // specifies a uint/sint texture binding,
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index 0c829f5..a1f34ac 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -60,9 +60,10 @@
 
 // Base component type of an inter-stage variable
 enum class InterStageComponentType {
-    Sint,
-    Uint,
-    Float,
+    I32,
+    U32,
+    F32,
+    F16,
 };
 
 enum class InterpolationType {
diff --git a/src/dawn/native/d3d12/AdapterD3D12.cpp b/src/dawn/native/d3d12/AdapterD3D12.cpp
index 0b9d284..b3b7bf4 100644
--- a/src/dawn/native/d3d12/AdapterD3D12.cpp
+++ b/src/dawn/native/d3d12/AdapterD3D12.cpp
@@ -381,6 +381,11 @@
         // Even this means that no vertex buffer view has been set in D3D12 backend.
         // https://crbug.com/dawn/1255
         D3D12_MESSAGE_ID_COMMAND_LIST_DRAW_VERTEX_BUFFER_NOT_SET,
+
+        // When using f16 in vertex attributes the debug layer may report float16_t as type
+        // `unknown`, resulting in a CREATEINPUTLAYOUT_TYPE_MISMATCH warning.
+        // https://crbug.com/tint/1473
+        D3D12_MESSAGE_ID_CREATEINPUTLAYOUT_TYPE_MISMATCH,
     };
 
     // Create a retrieval filter with a deny list to suppress messages.
diff --git a/src/dawn/native/webgpu_absl_format.cpp b/src/dawn/native/webgpu_absl_format.cpp
index 7df36ed..f313c3b 100644
--- a/src/dawn/native/webgpu_absl_format.cpp
+++ b/src/dawn/native/webgpu_absl_format.cpp
@@ -409,14 +409,17 @@
     const absl::FormatConversionSpec& spec,
     absl::FormatSink* s) {
     switch (value) {
-        case InterStageComponentType::Float:
-            s->Append("Float");
+        case InterStageComponentType::F32:
+            s->Append("f32");
             break;
-        case InterStageComponentType::Uint:
-            s->Append("Uint");
+        case InterStageComponentType::F16:
+            s->Append("f16");
             break;
-        case InterStageComponentType::Sint:
-            s->Append("Sint");
+        case InterStageComponentType::U32:
+            s->Append("u32");
+            break;
+        case InterStageComponentType::I32:
+            s->Append("i32");
             break;
     }
     return {true};
diff --git a/src/dawn/tests/end2end/ShaderF16Tests.cpp b/src/dawn/tests/end2end/ShaderF16Tests.cpp
index 42c881b..6722df9 100644
--- a/src/dawn/tests/end2end/ShaderF16Tests.cpp
+++ b/src/dawn/tests/end2end/ShaderF16Tests.cpp
@@ -19,12 +19,30 @@
 #include "dawn/utils/WGPUHelpers.h"
 
 namespace {
+
+constexpr uint32_t kRTSize = 16;
+constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
+
 using RequireShaderF16Feature = bool;
 DAWN_TEST_PARAM_STRUCT(ShaderF16TestsParams, RequireShaderF16Feature);
 
 }  // anonymous namespace
 
 class ShaderF16Tests : public DawnTestWithParams<ShaderF16TestsParams> {
+  public:
+    wgpu::Texture CreateDefault2DTexture() {
+        wgpu::TextureDescriptor descriptor;
+        descriptor.dimension = wgpu::TextureDimension::e2D;
+        descriptor.size.width = kRTSize;
+        descriptor.size.height = kRTSize;
+        descriptor.size.depthOrArrayLayers = 1;
+        descriptor.sampleCount = 1;
+        descriptor.format = kFormat;
+        descriptor.mipLevelCount = 1;
+        descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
+        return device.CreateTexture(&descriptor);
+    }
+
   protected:
     std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
         mIsShaderF16SupportedOnAdapter = SupportsFeatures({wgpu::FeatureName::ShaderF16});
@@ -58,6 +76,8 @@
     bool mUseDxcEnabledOrNonD3D12 = false;
 };
 
+// Test simple f16 arithmetic within shader with enable directive. The result should be as expect if
+// device enable f16 extension, otherwise a shader creation error should be caught.
 TEST_P(ShaderF16Tests, BasicShaderF16FeaturesTest) {
     const char* computeShader = R"(
         enable f16;
@@ -118,6 +138,308 @@
     EXPECT_BUFFER_U32_RANGE_EQ(expected, bufferOut, 0, 1);
 }
 
+// Test that fragment shader use f16 vector type as render target output.
+TEST_P(ShaderF16Tests, RenderPipelineIOF16_RenderTarget) {
+    // Skip if device don't support f16 extension.
+    DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::ShaderF16));
+
+    const char* shader = R"(
+enable f16;
+
+@vertex
+fn VSMain(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+    var pos = array<vec2<f32>, 3>(
+        vec2<f32>(-1.0,  1.0),
+        vec2<f32>( 1.0, -1.0),
+        vec2<f32>(-1.0, -1.0));
+
+    return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+}
+
+@fragment
+fn FSMain() -> @location(0) vec4<f16> {
+    // Paint it blue
+    return vec4<f16>(0.0, 0.0, 1.0, 1.0);
+})";
+
+    wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader);
+
+    // Create render pipeline.
+    wgpu::RenderPipeline pipeline;
+    {
+        utils::ComboRenderPipelineDescriptor descriptor;
+
+        descriptor.vertex.module = shaderModule;
+        descriptor.vertex.entryPoint = "VSMain";
+
+        descriptor.cFragment.module = shaderModule;
+        descriptor.cFragment.entryPoint = "FSMain";
+        descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
+        descriptor.cTargets[0].format = kFormat;
+
+        pipeline = device.CreateRenderPipeline(&descriptor);
+    }
+
+    wgpu::Texture renderTarget = CreateDefault2DTexture();
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+
+    {
+        // In the render pass we clear renderTarget to red and draw a blue triangle in the
+        // bottom left of renderTarget1.
+        utils::ComboRenderPassDescriptor renderPass({renderTarget.CreateView()});
+        renderPass.cColorAttachments[0].clearValue = {1.0f, 0.0f, 0.0f, 1.0f};
+
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.SetPipeline(pipeline);
+        pass.Draw(3);
+        pass.End();
+    }
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    // Validate that bottom left of render target is drawed to blue while upper right is still red
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlue, renderTarget, 1, kRTSize - 1);
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderTarget, kRTSize - 1, 1);
+}
+
+// Test using f16 types as vertex shader (user-defined) output and fragment shader
+// (user-defined) input.
+TEST_P(ShaderF16Tests, RenderPipelineIOF16_InterstageVariable) {
+    // Skip if device don't support f16 extension.
+    DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::ShaderF16));
+
+    const char* shader = R"(
+enable f16;
+
+struct VSOutput{
+    @builtin(position)
+    pos: vec4<f32>,
+    @location(3)
+    color_vsout: vec4<f16>,
+}
+
+@vertex
+fn VSMain(@builtin(vertex_index) VertexIndex : u32) -> VSOutput {
+    var pos = array<vec2<f32>, 3>(
+        vec2<f32>(-1.0,  1.0),
+        vec2<f32>( 1.0, -1.0),
+        vec2<f32>(-1.0, -1.0));
+
+    // Blue
+    var color = vec4<f16>(0.0h, 0.0h, 1.0h, 1.0h);
+
+    var result: VSOutput;
+    result.pos = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+    result.color_vsout = color;
+
+    return result;
+}
+
+struct FSInput{
+    @location(3)
+    color_fsin: vec4<f16>,
+}
+
+@fragment
+fn FSMain(fsInput: FSInput) -> @location(0) vec4<f32> {
+    // Paint it with given color
+    return vec4<f32>(fsInput.color_fsin);
+})";
+
+    wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader);
+
+    // Create render pipeline.
+    wgpu::RenderPipeline pipeline;
+    {
+        utils::ComboRenderPipelineDescriptor descriptor;
+
+        descriptor.vertex.module = shaderModule;
+        descriptor.vertex.entryPoint = "VSMain";
+
+        descriptor.cFragment.module = shaderModule;
+        descriptor.cFragment.entryPoint = "FSMain";
+        descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
+        descriptor.cTargets[0].format = kFormat;
+
+        pipeline = device.CreateRenderPipeline(&descriptor);
+    }
+
+    wgpu::Texture renderTarget = CreateDefault2DTexture();
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+
+    {
+        // In the first render pass we clear renderTarget1 to red and draw a blue triangle in the
+        // bottom left of renderTarget1.
+        utils::ComboRenderPassDescriptor renderPass({renderTarget.CreateView()});
+        renderPass.cColorAttachments[0].clearValue = {1.0f, 0.0f, 0.0f, 1.0f};
+
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.SetPipeline(pipeline);
+        pass.Draw(3);
+        pass.End();
+    }
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    // Validate that bottom left of render target is drawed to blue while upper right is still red
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlue, renderTarget, 1, kRTSize - 1);
+    EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderTarget, kRTSize - 1, 1);
+}
+
+// Test using f16 types as vertex shader user-defined input (vertex attributes), draw points of
+// different color given as vertex attributes.
+TEST_P(ShaderF16Tests, RenderPipelineIOF16_VertexAttribute) {
+    // Skip if device don't support f16 extension.
+    DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::ShaderF16));
+
+    const char* shader = R"(
+enable f16;
+
+struct VSInput {
+    // position / 2.0
+    @location(0) pos_half : vec2<f16>,
+    // color / 4.0
+    @location(1) color_quarter : vec4<f16>,
+}
+
+struct VSOutput {
+    @builtin(position) pos : vec4<f32>,
+    @location(0) color : vec4<f32>,
+}
+
+@vertex
+fn VSMain(in: VSInput) -> VSOutput {
+    return VSOutput(vec4<f32>(vec2<f32>(in.pos_half * 2.0h), 0.0, 1.0), vec4<f32>(in.color_quarter * 4.0h));
+}
+
+@fragment
+fn FSMain(@location(0) color : vec4<f32>) -> @location(0) vec4<f32> {
+    return color;
+})";
+
+    wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, shader);
+
+    constexpr uint32_t kPointCount = 8;
+
+    // Position (divided by 2.0) for points on horizontal line
+    std::vector<float> positionData;
+    constexpr float xStep = 2.0 / kPointCount;
+    constexpr float xBias = -1.0 + xStep / 2.0f;
+    for (uint32_t i = 0; i < kPointCount; i++) {
+        // X position, divided by 2.0
+        positionData.push_back((xBias + xStep * i) / 2.0f);
+        // Y position (0.0f) divided by 2.0
+        positionData.push_back(0.0f);
+    }
+
+    // Expected color for each point
+    using RGBA8 = utils::RGBA8;
+    std::vector<RGBA8> colors = {
+        RGBA8::kBlack,
+        RGBA8::kRed,
+        RGBA8::kGreen,
+        RGBA8::kBlue,
+        RGBA8::kYellow,
+        RGBA8::kWhite,
+        RGBA8(96, 192, 176, 255),
+        RGBA8(184, 108, 184, 255),
+    };
+
+    ASSERT(colors.size() == kPointCount);
+    // Color (divided by 4.0) for each point
+    std::vector<float> colorData;
+    for (RGBA8& color : colors) {
+        colorData.push_back(color.r / 255.0 / 4.0);
+        colorData.push_back(color.g / 255.0 / 4.0);
+        colorData.push_back(color.b / 255.0 / 4.0);
+        colorData.push_back(color.a / 255.0 / 4.0);
+    }
+
+    // Store the data as float32x2 and float32x4 in vertex buffer, which should be convert to
+    // corresponding WGSL type vec2<f16> and vec4<f16> by driver.
+    // Buffer for pos_half
+    wgpu::Buffer vertexBufferPos = utils::CreateBufferFromData(
+        device, positionData.data(), 2 * kPointCount * sizeof(float), wgpu::BufferUsage::Vertex);
+    // Buffer for color_quarter
+    wgpu::Buffer vertexBufferColor = utils::CreateBufferFromData(
+        device, colorData.data(), 4 * kPointCount * sizeof(float), wgpu::BufferUsage::Vertex);
+
+    // Create render pipeline.
+    wgpu::RenderPipeline pipeline;
+    {
+        utils::ComboRenderPipelineDescriptor descriptor;
+
+        descriptor.vertex.module = shaderModule;
+        descriptor.vertex.entryPoint = "VSMain";
+        descriptor.vertex.bufferCount = 2;
+        // Interprete the vertex buffer data as Float32x2 and Float32x4, and the result should be
+        // converted to vec2<f16> and vec4<f16>
+        descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x2;
+        descriptor.cAttributes[0].offset = 0;
+        descriptor.cAttributes[0].shaderLocation = 0;
+        descriptor.cBuffers[0].stepMode = wgpu::VertexStepMode::Vertex;
+        descriptor.cBuffers[0].arrayStride = 8;
+        descriptor.cBuffers[0].attributeCount = 1;
+        descriptor.cBuffers[0].attributes = &descriptor.cAttributes[0];
+        descriptor.cAttributes[1].format = wgpu::VertexFormat::Float32x4;
+        descriptor.cAttributes[1].offset = 0;
+        descriptor.cAttributes[1].shaderLocation = 1;
+        descriptor.cBuffers[1].stepMode = wgpu::VertexStepMode::Vertex;
+        descriptor.cBuffers[1].arrayStride = 16;
+        descriptor.cBuffers[1].attributeCount = 1;
+        descriptor.cBuffers[1].attributes = &descriptor.cAttributes[1];
+
+        descriptor.cFragment.module = shaderModule;
+        descriptor.cFragment.entryPoint = "FSMain";
+        descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
+        descriptor.cTargets[0].format = kFormat;
+
+        pipeline = device.CreateRenderPipeline(&descriptor);
+    }
+
+    // Create a render target of horizontal line
+    wgpu::Texture renderTarget;
+    {
+        wgpu::TextureDescriptor descriptor;
+        descriptor.dimension = wgpu::TextureDimension::e2D;
+        descriptor.size.width = kPointCount;
+        descriptor.size.height = 1;
+        descriptor.size.depthOrArrayLayers = 1;
+        descriptor.sampleCount = 1;
+        descriptor.format = kFormat;
+        descriptor.mipLevelCount = 1;
+        descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
+        renderTarget = device.CreateTexture(&descriptor);
+    }
+
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+
+    {
+        // Clear renderTarget to zero and draw points.
+        utils::ComboRenderPassDescriptor renderPass({renderTarget.CreateView()});
+        renderPass.cColorAttachments[0].clearValue = {0.0f, 0.0f, 0.0f, 0.0f};
+
+        wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
+        pass.SetPipeline(pipeline);
+        pass.SetVertexBuffer(0, vertexBufferPos);
+        pass.SetVertexBuffer(1, vertexBufferColor);
+        pass.Draw(kPointCount);
+        pass.End();
+    }
+
+    wgpu::CommandBuffer commands = encoder.Finish();
+    queue.Submit(1, &commands);
+
+    // Validate the color of each point
+    for (uint32_t i = 0; i < kPointCount; i++) {
+        EXPECT_PIXEL_RGBA8_EQ(colors[i], renderTarget, i, 0);
+    }
+}
+
 // DawnTestBase::CreateDeviceImpl always disable disallow_unsafe_apis toggle.
 DAWN_INSTANTIATE_TEST_P(ShaderF16Tests,
                         {
diff --git a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
index 927f05b..7e8a19f 100644
--- a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -24,6 +24,26 @@
 
 class RenderPipelineValidationTest : public ValidationTest {
   protected:
+    WGPUDevice CreateTestDevice(dawn::native::Adapter dawnAdapter) override {
+        // Disabled disallowing unsafe APIs so we can test ShaderF16 feature.
+        const char* forceDisabledToggle[] = {"disallow_unsafe_apis"};
+
+        wgpu::DeviceDescriptor descriptor;
+        wgpu::FeatureName requiredFeatures[1] = {wgpu::FeatureName::ShaderF16};
+        descriptor.requiredFeatures = requiredFeatures;
+        descriptor.requiredFeaturesCount = 1;
+
+        wgpu::DawnTogglesDeviceDescriptor togglesDesc;
+        descriptor.nextInChain = &togglesDesc;
+
+        togglesDesc.forceEnabledToggles = nullptr;
+        togglesDesc.forceEnabledTogglesCount = 0;
+        togglesDesc.forceDisabledToggles = forceDisabledToggle;
+        togglesDesc.forceDisabledTogglesCount = 1;
+
+        return dawnAdapter.CreateDevice(&descriptor);
+    }
+
     void SetUp() override {
         ValidationTest::SetUp();
 
@@ -326,30 +346,53 @@
 
 // Tests that the format of the color state descriptor must match the output of the fragment shader.
 TEST_F(RenderPipelineValidationTest, FragmentOutputFormatCompatibility) {
-    std::array<const char*, 3> kScalarTypes = {{"f32", "i32", "u32"}};
-    std::array<wgpu::TextureFormat, 3> kColorFormats = {{wgpu::TextureFormat::RGBA8Unorm,
-                                                         wgpu::TextureFormat::RGBA8Sint,
-                                                         wgpu::TextureFormat::RGBA8Uint}};
+    std::vector<std::vector<std::string>> kScalarTypeLists = {// Float scalar types
+                                                              {"f32", "f16"},
+                                                              // Sint scalar type
+                                                              {"i32"},
+                                                              // Uint scalar type
+                                                              {"u32"}};
 
-    for (size_t i = 0; i < kScalarTypes.size(); ++i) {
-        utils::ComboRenderPipelineDescriptor descriptor;
-        descriptor.vertex.module = vsModule;
-        std::ostringstream stream;
-        stream << R"(
+    std::vector<std::vector<wgpu::TextureFormat>> kColorFormatLists = {
+        // Float color formats
+        {wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::RGBA16Float,
+         wgpu::TextureFormat::RGBA32Float},
+        // Sint color formats
+        {wgpu::TextureFormat::RGBA8Sint, wgpu::TextureFormat::RGBA16Sint,
+         wgpu::TextureFormat::RGBA32Sint},
+        // Uint color formats
+        {wgpu::TextureFormat::RGBA8Uint, wgpu::TextureFormat::RGBA16Uint,
+         wgpu::TextureFormat::RGBA32Uint}};
+
+    for (size_t i = 0; i < kScalarTypeLists.size(); ++i) {
+        for (const std::string& scalarType : kScalarTypeLists[i]) {
+            utils::ComboRenderPipelineDescriptor descriptor;
+            descriptor.vertex.module = vsModule;
+            std::ostringstream stream;
+
+            // Enable f16 extension if needed.
+            if (scalarType == "f16") {
+                stream << "enable f16;\n\n";
+            }
+            stream << R"(
             @fragment fn main() -> @location(0) vec4<)"
-               << kScalarTypes[i] << R"(> {
+                   << scalarType << R"(> {
                 var result : vec4<)"
-               << kScalarTypes[i] << R"(>;
+                   << scalarType << R"(>;
                 return result;
             })";
-        descriptor.cFragment.module = utils::CreateShaderModule(device, stream.str().c_str());
 
-        for (size_t j = 0; j < kColorFormats.size(); ++j) {
-            descriptor.cTargets[0].format = kColorFormats[j];
-            if (i == j) {
-                device.CreateRenderPipeline(&descriptor);
-            } else {
-                ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+            descriptor.cFragment.module = utils::CreateShaderModule(device, stream.str().c_str());
+
+            for (size_t j = 0; j < kColorFormatLists.size(); ++j) {
+                for (wgpu::TextureFormat textureFormat : kColorFormatLists[j]) {
+                    descriptor.cTargets[0].format = textureFormat;
+                    if (i == j) {
+                        device.CreateRenderPipeline(&descriptor);
+                    } else {
+                        ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+                    }
+                }
             }
         }
     }
@@ -1446,12 +1489,13 @@
 // Tests that creating render pipeline should fail when the type of a vertex stage output variable
 // doesn't match the type of the fragment stage input variable at the same location.
 TEST_F(InterStageVariableMatchingValidationTest, DifferentTypeAtSameLocation) {
-    constexpr std::array<const char*, 12> kTypes = {{"f32", "vec2<f32>", "vec3<f32>", "vec4<f32>",
+    constexpr std::array<const char*, 16> kTypes = {{"f32", "vec2<f32>", "vec3<f32>", "vec4<f32>",
+                                                     "f16", "vec2<f16>", "vec3<f16>", "vec4<f16>",
                                                      "i32", "vec2<i32>", "vec3<i32>", "vec4<i32>",
                                                      "u32", "vec2<u32>", "vec3<u32>", "vec4<u32>"}};
 
-    std::array<wgpu::ShaderModule, 12> vertexModules;
-    std::array<wgpu::ShaderModule, 12> fragmentModules;
+    std::array<wgpu::ShaderModule, 16> vertexModules;
+    std::array<wgpu::ShaderModule, 16> fragmentModules;
     for (uint32_t i = 0; i < kTypes.size(); ++i) {
         std::string interfaceDeclaration;
         {
@@ -1460,9 +1504,12 @@
                     << std::endl;
             interfaceDeclaration = sstream.str();
         }
+
+        std::string extensionDeclaration = "enable f16;\n\n";
+
         {
             std::ostringstream vertexStream;
-            vertexStream << interfaceDeclaration << R"(
+            vertexStream << extensionDeclaration << interfaceDeclaration << R"(
                     @builtin(position) pos: vec4<f32>,
                 }
                 @vertex fn main() -> A {
@@ -1474,7 +1521,7 @@
         }
         {
             std::ostringstream fragmentStream;
-            fragmentStream << interfaceDeclaration << R"(
+            fragmentStream << extensionDeclaration << interfaceDeclaration << R"(
                 }
                 @fragment fn main(fragmentIn: A) -> @location(0) vec4<f32> {
                     return vec4<f32>(0.0, 0.0, 0.0, 1.0);
diff --git a/src/tint/inspector/entry_point.h b/src/tint/inspector/entry_point.h
index 4a4706b..eabe601 100644
--- a/src/tint/inspector/entry_point.h
+++ b/src/tint/inspector/entry_point.h
@@ -30,9 +30,10 @@
 /// Base component type of a stage variable.
 enum class ComponentType {
     kUnknown = -1,
-    kFloat,
-    kUInt,
-    kSInt,
+    kF32,
+    kU32,
+    kI32,
+    kF16,
 };
 
 /// Composition of components of a stage variable.
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index ea5b3ed..b893ed1 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -69,41 +69,48 @@
 }
 
 std::tuple<ComponentType, CompositionType> CalculateComponentAndComposition(const sem::Type* type) {
-    if (type->is_float_scalar()) {
-        return {ComponentType::kFloat, CompositionType::kScalar};
-    } else if (type->is_float_vector()) {
-        auto* vec = type->As<sem::Vector>();
-        if (vec->Width() == 2) {
-            return {ComponentType::kFloat, CompositionType::kVec2};
-        } else if (vec->Width() == 3) {
-            return {ComponentType::kFloat, CompositionType::kVec3};
-        } else if (vec->Width() == 4) {
-            return {ComponentType::kFloat, CompositionType::kVec4};
+    // entry point in/out variables must of numeric scalar or vector types.
+    TINT_ASSERT(Inspector, type->is_numeric_scalar_or_vector());
+
+    ComponentType componentType = Switch(
+        sem::Type::DeepestElementOf(type),  //
+        [&](const sem::F32*) { return ComponentType::kF32; },
+        [&](const sem::F16*) { return ComponentType::kF16; },
+        [&](const sem::I32*) { return ComponentType::kI32; },
+        [&](const sem::U32*) { return ComponentType::kU32; },
+        [&](Default) {
+            tint::diag::List diagnostics;
+            TINT_UNREACHABLE(Inspector, diagnostics) << "unhandled component type";
+            return ComponentType::kUnknown;
+        });
+
+    CompositionType compositionType;
+    if (auto* vec = type->As<sem::Vector>()) {
+        switch (vec->Width()) {
+            case 2: {
+                compositionType = CompositionType::kVec2;
+                break;
+            }
+            case 3: {
+                compositionType = CompositionType::kVec3;
+                break;
+            }
+            case 4: {
+                compositionType = CompositionType::kVec4;
+                break;
+            }
+            default: {
+                tint::diag::List diagnostics;
+                TINT_UNREACHABLE(Inspector, diagnostics) << "unhandled composition type";
+                compositionType = CompositionType::kUnknown;
+                break;
+            }
         }
-    } else if (type->is_unsigned_integer_scalar()) {
-        return {ComponentType::kUInt, CompositionType::kScalar};
-    } else if (type->is_unsigned_integer_vector()) {
-        auto* vec = type->As<sem::Vector>();
-        if (vec->Width() == 2) {
-            return {ComponentType::kUInt, CompositionType::kVec2};
-        } else if (vec->Width() == 3) {
-            return {ComponentType::kUInt, CompositionType::kVec3};
-        } else if (vec->Width() == 4) {
-            return {ComponentType::kUInt, CompositionType::kVec4};
-        }
-    } else if (type->is_signed_integer_scalar()) {
-        return {ComponentType::kSInt, CompositionType::kScalar};
-    } else if (type->is_signed_integer_vector()) {
-        auto* vec = type->As<sem::Vector>();
-        if (vec->Width() == 2) {
-            return {ComponentType::kSInt, CompositionType::kVec2};
-        } else if (vec->Width() == 3) {
-            return {ComponentType::kSInt, CompositionType::kVec3};
-        } else if (vec->Width() == 4) {
-            return {ComponentType::kSInt, CompositionType::kVec4};
-        }
+    } else {
+        compositionType = CompositionType::kScalar;
     }
-    return {ComponentType::kUnknown, CompositionType::kUnknown};
+
+    return {componentType, compositionType};
 }
 
 std::tuple<InterpolationType, InterpolationSampling> CalculateInterpolationData(
diff --git a/src/tint/inspector/inspector_test.cc b/src/tint/inspector/inspector_test.cc
index 83302e2..78cfb5a 100644
--- a/src/tint/inspector/inspector_test.cc
+++ b/src/tint/inspector/inspector_test.cc
@@ -287,6 +287,10 @@
     std::tie(component, composition) = GetParam();
     std::function<const ast::Type*()> tint_type = GetTypeFunction(component, composition);
 
+    if (component == ComponentType::kF16) {
+        Enable(ast::Extension::kF16);
+    }
+
     auto* in_var = Param("in_var", tint_type(),
                          utils::Vector{
                              Location(0_u),
@@ -323,9 +327,10 @@
 }
 INSTANTIATE_TEST_SUITE_P(InspectorGetEntryPointTest,
                          InspectorGetEntryPointComponentAndCompositionTest,
-                         testing::Combine(testing::Values(ComponentType::kFloat,
-                                                          ComponentType::kSInt,
-                                                          ComponentType::kUInt),
+                         testing::Combine(testing::Values(ComponentType::kF32,
+                                                          ComponentType::kI32,
+                                                          ComponentType::kU32,
+                                                          ComponentType::kF16),
                                           testing::Values(CompositionType::kScalar,
                                                           CompositionType::kVec2,
                                                           CompositionType::kVec3,
@@ -369,23 +374,23 @@
     EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
     EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[0].interpolation_type);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
     EXPECT_EQ("in_var1", result[0].input_variables[1].name);
     EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
     EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
     EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[1].interpolation_type);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
     EXPECT_EQ("in_var4", result[0].input_variables[2].name);
     EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
     EXPECT_EQ(4u, result[0].input_variables[2].location_attribute);
     EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[2].interpolation_type);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[2].component_type);
 
     ASSERT_EQ(1u, result[0].output_variables.size());
     EXPECT_EQ("<retval>", result[0].output_variables[0].name);
     EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) {
@@ -433,26 +438,26 @@
     EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
     EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[0].interpolation_type);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
 
     ASSERT_EQ(1u, result[0].output_variables.size());
     EXPECT_EQ("<retval>", result[0].output_variables[0].name);
     EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
 
     ASSERT_EQ(1u, result[1].input_variables.size());
     EXPECT_EQ("in_var_bar", result[1].input_variables[0].name);
     EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
     EXPECT_EQ(InterpolationType::kFlat, result[1].input_variables[0].interpolation_type);
-    EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[1].input_variables[0].component_type);
 
     ASSERT_EQ(1u, result[1].output_variables.size());
     EXPECT_EQ("<retval>", result[1].output_variables[0].name);
     EXPECT_TRUE(result[1].output_variables[0].has_location_attribute);
     EXPECT_EQ(1u, result[1].output_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[1].output_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[1].output_variables[0].component_type);
 }
 
 TEST_F(InspectorGetEntryPointTest, BuiltInsNotStageVariables) {
@@ -485,7 +490,7 @@
     EXPECT_EQ("in_var1", result[0].input_variables[0].name);
     EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kF32, result[0].input_variables[0].component_type);
 
     ASSERT_EQ(0u, result[0].output_variables.size());
 }
@@ -517,21 +522,21 @@
     EXPECT_EQ("param.a", result[0].input_variables[0].name);
     EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
     EXPECT_EQ("param.b", result[0].input_variables[1].name);
     EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
     EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
 
     ASSERT_EQ(2u, result[0].output_variables.size());
     EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
     EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
     EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
     EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
     EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
 }
 
 TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) {
@@ -563,21 +568,21 @@
     EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
     EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
     EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
     EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
     EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
 
     ASSERT_EQ(2u, result[1].input_variables.size());
     EXPECT_EQ("param.a", result[1].input_variables[0].name);
     EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[1].input_variables[0].component_type);
     EXPECT_EQ("param.b", result[1].input_variables[1].name);
     EXPECT_TRUE(result[1].input_variables[1].has_location_attribute);
     EXPECT_EQ(1u, result[1].input_variables[1].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[1].input_variables[1].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[1].input_variables[1].component_type);
 
     ASSERT_EQ(0u, result[1].output_variables.size());
 }
@@ -615,33 +620,33 @@
     EXPECT_EQ("param_a.a", result[0].input_variables[0].name);
     EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
     EXPECT_EQ("param_a.b", result[0].input_variables[1].name);
     EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
     EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[1].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
     EXPECT_EQ("param_b.a", result[0].input_variables[2].name);
     EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
     EXPECT_EQ(2u, result[0].input_variables[2].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].input_variables[2].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[2].component_type);
     EXPECT_EQ("param_c", result[0].input_variables[3].name);
     EXPECT_TRUE(result[0].input_variables[3].has_location_attribute);
     EXPECT_EQ(3u, result[0].input_variables[3].location_attribute);
-    EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[3].component_type);
+    EXPECT_EQ(ComponentType::kF32, result[0].input_variables[3].component_type);
     EXPECT_EQ("param_d", result[0].input_variables[4].name);
     EXPECT_TRUE(result[0].input_variables[4].has_location_attribute);
     EXPECT_EQ(4u, result[0].input_variables[4].location_attribute);
-    EXPECT_EQ(ComponentType::kFloat, result[0].input_variables[4].component_type);
+    EXPECT_EQ(ComponentType::kF32, result[0].input_variables[4].component_type);
 
     ASSERT_EQ(2u, result[0].output_variables.size());
     EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
     EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
     EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[0].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
     EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
     EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
     EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
-    EXPECT_EQ(ComponentType::kUInt, result[0].output_variables[1].component_type);
+    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
 }
 
 TEST_F(InspectorGetEntryPointTest, OverrideUnreferenced) {
diff --git a/src/tint/inspector/test_inspector_builder.cc b/src/tint/inspector/test_inspector_builder.cc
index 79122dc..97ae097 100644
--- a/src/tint/inspector/test_inspector_builder.cc
+++ b/src/tint/inspector/test_inspector_builder.cc
@@ -307,15 +307,18 @@
                                                                     CompositionType composition) {
     std::function<const ast::Type*()> func;
     switch (component) {
-        case ComponentType::kFloat:
+        case ComponentType::kF32:
             func = [this]() -> const ast::Type* { return ty.f32(); };
             break;
-        case ComponentType::kSInt:
+        case ComponentType::kI32:
             func = [this]() -> const ast::Type* { return ty.i32(); };
             break;
-        case ComponentType::kUInt:
+        case ComponentType::kU32:
             func = [this]() -> const ast::Type* { return ty.u32(); };
             break;
+        case ComponentType::kF16:
+            func = [this]() -> const ast::Type* { return ty.f16(); };
+            break;
         case ComponentType::kUnknown:
             return []() -> const ast::Type* { return nullptr; };
     }
diff --git a/src/tint/resolver/entry_point_validation_test.cc b/src/tint/resolver/entry_point_validation_test.cc
index 79b41d7..cd49ded 100644
--- a/src/tint/resolver/entry_point_validation_test.cc
+++ b/src/tint/resolver/entry_point_validation_test.cc
@@ -606,17 +606,14 @@
     ParamsFor<alias<i32>>(true),    //
     ParamsFor<alias<u32>>(true),    //
     ParamsFor<alias<bool>>(false),  //
-    // Currently entry point IO of f16 types are not implemented yet.
-    // TODO(tint:1473, tint:1502): Change f16 and vecN<f16> cases to valid after f16 is supported in
-    // entry point IO.
-    ParamsFor<f16>(false),          //
-    ParamsFor<vec2<f16>>(false),    //
-    ParamsFor<vec3<f16>>(false),    //
-    ParamsFor<vec4<f16>>(false),    //
+    ParamsFor<f16>(true),           //
+    ParamsFor<vec2<f16>>(true),     //
+    ParamsFor<vec3<f16>>(true),     //
+    ParamsFor<vec4<f16>>(true),     //
     ParamsFor<mat2x2<f16>>(false),  //
     ParamsFor<mat3x3<f16>>(false),  //
     ParamsFor<mat4x4<f16>>(false),  //
-    ParamsFor<alias<f16>>(false),   //
+    ParamsFor<alias<f16>>(true),    //
 };
 
 TEST_P(TypeValidationTest, BareInputs) {
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 8da1726..558217d 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -1068,13 +1068,6 @@
                                                      ParamOrRetType param_or_ret,
                                                      bool is_struct_member,
                                                      std::optional<uint32_t> location) {
-        // Temporally forbid using f16 types in entry point IO.
-        // TODO(tint:1473, tint:1502): Remove this error after f16 is supported in entry point IO.
-        if (Is<sem::F16>(sem::Type::DeepestElementOf(ty))) {
-            AddError("entry point IO of f16 types is not implemented yet", source);
-            return false;
-        }
-
         // Scan attributes for pipeline IO attributes.
         // Check for overlap with attributes that have been seen previously.
         const ast::Attribute* pipeline_io_attribute = nullptr;
diff --git a/src/tint/transform/vertex_pulling.cc b/src/tint/transform/vertex_pulling.cc
index 0196002..e213ac6 100644
--- a/src/tint/transform/vertex_pulling.cc
+++ b/src/tint/transform/vertex_pulling.cc
@@ -41,6 +41,7 @@
     kU32,
     kI32,
     kF32,
+    kF16,
 };
 
 /// The data type of a vertex format.
@@ -138,6 +139,7 @@
 bool IsTypeCompatible(AttributeWGSLType wgslType, VertexFormatType vertexFormatType) {
     switch (wgslType.base_type) {
         case BaseWGSLType::kF32:
+        case BaseWGSLType::kF16:
             return (vertexFormatType.base_type == VertexDataType::kFloat);
         case BaseWGSLType::kU32:
             return (vertexFormatType.base_type == VertexDataType::kUInt);
@@ -149,19 +151,26 @@
 }
 
 AttributeWGSLType WGSLTypeOf(const sem::Type* ty) {
-    if (ty->Is<sem::I32>()) {
-        return {BaseWGSLType::kI32, 1};
-    }
-    if (ty->Is<sem::U32>()) {
-        return {BaseWGSLType::kU32, 1};
-    }
-    if (ty->Is<sem::F32>()) {
-        return {BaseWGSLType::kF32, 1};
-    }
-    if (auto* vec = ty->As<sem::Vector>()) {
-        return {WGSLTypeOf(vec->type()).base_type, vec->Width()};
-    }
-    return {BaseWGSLType::kInvalid, 0};
+    return Switch(
+        ty,
+        [](const sem::I32*) -> AttributeWGSLType {
+            return {BaseWGSLType::kI32, 1};
+        },
+        [](const sem::U32*) -> AttributeWGSLType {
+            return {BaseWGSLType::kU32, 1};
+        },
+        [](const sem::F32*) -> AttributeWGSLType {
+            return {BaseWGSLType::kF32, 1};
+        },
+        [](const sem::F16*) -> AttributeWGSLType {
+            return {BaseWGSLType::kF16, 1};
+        },
+        [](const sem::Vector* vec) -> AttributeWGSLType {
+            return {WGSLTypeOf(vec->type()).base_type, vec->Width()};
+        },
+        [](Default) -> AttributeWGSLType {
+            return {BaseWGSLType::kInvalid, 0};
+        });
 }
 
 VertexFormatType VertexFormatTypeOf(VertexFormat format) {
@@ -378,9 +387,22 @@
 
                 // Load the attribute value according to vertex format and convert the element type
                 // of result to match target WGSL variable. The result of `Fetch` should be of WGSL
-                // types `f32`, `i32`, `u32`, and their vectors.
+                // types `f32`, `i32`, `u32`, and their vectors, while WGSL variable can be of
+                // `f16`.
                 auto* fetch = Fetch(buffer_array_base, attribute_desc.offset, buffer_idx,
                                     attribute_desc.format);
+                // Convert the fetched scalar/vector if WGSL variable is of `f16` types
+                if (var_dt.base_type == BaseWGSLType::kF16) {
+                    // The type of the same element number of base type of target WGSL variable
+                    const ast::Type* loaded_data_target_type;
+                    if (fmt_dt.width == 1) {
+                        loaded_data_target_type = b.ty.f16();
+                    } else {
+                        loaded_data_target_type = b.ty.vec(b.ty.f16(), fmt_dt.width);
+                    }
+
+                    fetch = b.Construct(loaded_data_target_type, fetch);
+                }
 
                 // The attribute value may not be of the desired vector width. If it is not, we'll
                 // need to either reduce the width with a swizzle, or append 0's and / or a 1.
diff --git a/src/tint/transform/vertex_pulling_test.cc b/src/tint/transform/vertex_pulling_test.cc
index 54c348e..a6dc2d2 100644
--- a/src/tint/transform/vertex_pulling_test.cc
+++ b/src/tint/transform/vertex_pulling_test.cc
@@ -736,6 +736,63 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(VertexPullingTest, FloatVectorAttributes_F16) {
+    auto* src = R"(
+enable f16;
+
+@vertex
+fn main(@location(0) var_a : vec2<f16>,
+        @location(1) var_b : vec3<f16>,
+        @location(2) var_c : vec4<f16>
+        ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+    auto* expect = R"(
+enable f16;
+
+struct TintVertexData {
+  tint_vertex_data : array<u32>,
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@binding(1) @group(4) var<storage, read> tint_pulling_vertex_buffer_1 : TintVertexData;
+
+@binding(2) @group(4) var<storage, read> tint_pulling_vertex_buffer_2 : TintVertexData;
+
+@vertex
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var var_a : vec2<f16>;
+  var var_b : vec3<f16>;
+  var var_c : vec4<f16>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 2u);
+    var_a = vec2<f16>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[buffer_array_base_0]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 1u)])));
+    let buffer_array_base_1 = (tint_pulling_vertex_index * 3u);
+    var_b = vec3<f16>(vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[buffer_array_base_1]), bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[(buffer_array_base_1 + 1u)]), bitcast<f32>(tint_pulling_vertex_buffer_1.tint_vertex_data[(buffer_array_base_1 + 2u)])));
+    let buffer_array_base_2 = (tint_pulling_vertex_index * 4u);
+    var_c = vec4<f16>(vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_2.tint_vertex_data[buffer_array_base_2]), unpack2x16float(tint_pulling_vertex_buffer_2.tint_vertex_data[(buffer_array_base_2 + 1u)])));
+  }
+  return vec4<f32>();
+}
+)";
+
+    VertexPulling::Config cfg;
+    cfg.vertex_state = {{
+        {8, VertexStepMode::kVertex, {{VertexFormat::kFloat32x2, 0, 0}}},
+        {12, VertexStepMode::kVertex, {{VertexFormat::kFloat32x3, 0, 1}}},
+        {16, VertexStepMode::kVertex, {{VertexFormat::kFloat16x4, 0, 2}}},
+    }};
+
+    DataMap data;
+    data.Add<VertexPulling::Config>(cfg);
+    auto got = Run<VertexPulling>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(VertexPullingTest, AttemptSymbolCollision) {
     auto* src = R"(
 @vertex
@@ -1019,6 +1076,104 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(VertexPullingTest, FormatsAligned_Float_F16) {
+    auto* src = R"(
+enable f16;
+
+@vertex
+fn main(
+    @location(0) unorm8x2 : vec2<f16>,
+    @location(1) unorm8x4 : vec4<f16>,
+    @location(2) snorm8x2 : vec2<f16>,
+    @location(3) snorm8x4 : vec4<f16>,
+    @location(4) unorm16x2 : vec2<f16>,
+    @location(5) unorm16x4 : vec4<f16>,
+    @location(6) snorm16x2 : vec2<f16>,
+    @location(7) snorm16x4 : vec4<f16>,
+    @location(8) float16x2 : vec2<f16>,
+    @location(9) float16x4 : vec4<f16>,
+    @location(10) float32 : f16,
+    @location(11) float32x2 : vec2<f16>,
+    @location(12) float32x3 : vec3<f16>,
+    @location(13) float32x4 : vec4<f16>,
+  ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    auto* expect = R"(
+enable f16;
+
+struct TintVertexData {
+  tint_vertex_data : array<u32>,
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@vertex
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var unorm8x2 : vec2<f16>;
+  var unorm8x4 : vec4<f16>;
+  var snorm8x2 : vec2<f16>;
+  var snorm8x4 : vec4<f16>;
+  var unorm16x2 : vec2<f16>;
+  var unorm16x4 : vec4<f16>;
+  var snorm16x2 : vec2<f16>;
+  var snorm16x4 : vec4<f16>;
+  var float16x2 : vec2<f16>;
+  var float16x4 : vec4<f16>;
+  var float32 : f16;
+  var float32x2 : vec2<f16>;
+  var float32x3 : vec3<f16>;
+  var float32x4 : vec4<f16>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
+    unorm8x2 = vec2<f16>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy);
+    unorm8x4 = vec4<f16>(unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]));
+    snorm8x2 = vec2<f16>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy);
+    snorm8x4 = vec4<f16>(unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]));
+    unorm16x2 = vec2<f16>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]));
+    unorm16x4 = vec4<f16>(vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])));
+    snorm16x2 = vec2<f16>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]));
+    snorm16x4 = vec4<f16>(vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])));
+    float16x2 = vec2<f16>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]));
+    float16x4 = vec4<f16>(vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])));
+    float32 = f16(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]));
+    float32x2 = vec2<f16>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])));
+    float32x3 = vec3<f16>(vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)])));
+    float32x4 = vec4<f16>(vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)])));
+  }
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    VertexPulling::Config cfg;
+    cfg.vertex_state = {{{256,
+                          VertexStepMode::kVertex,
+                          {
+                              {VertexFormat::kUnorm8x2, 64, 0},
+                              {VertexFormat::kUnorm8x4, 64, 1},
+                              {VertexFormat::kSnorm8x2, 64, 2},
+                              {VertexFormat::kSnorm8x4, 64, 3},
+                              {VertexFormat::kUnorm16x2, 64, 4},
+                              {VertexFormat::kUnorm16x4, 64, 5},
+                              {VertexFormat::kSnorm16x2, 64, 6},
+                              {VertexFormat::kSnorm16x4, 64, 7},
+                              {VertexFormat::kFloat16x2, 64, 8},
+                              {VertexFormat::kFloat16x4, 64, 9},
+                              {VertexFormat::kFloat32, 64, 10},
+                              {VertexFormat::kFloat32x2, 64, 11},
+                              {VertexFormat::kFloat32x3, 64, 12},
+                              {VertexFormat::kFloat32x4, 64, 13},
+                          }}}};
+
+    DataMap data;
+    data.Add<VertexPulling::Config>(cfg);
+    auto got = Run<VertexPulling>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(VertexPullingTest, FormatsUnaligned_SInt) {
     auto* src = R"(
 @vertex
@@ -1253,6 +1408,104 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(VertexPullingTest, FormatsUnaligned_Float_F16) {
+    auto* src = R"(
+enable f16;
+
+@vertex
+fn main(
+    @location(0) unorm8x2 : vec2<f16>,
+    @location(1) unorm8x4 : vec4<f16>,
+    @location(2) snorm8x2 : vec2<f16>,
+    @location(3) snorm8x4 : vec4<f16>,
+    @location(4) unorm16x2 : vec2<f16>,
+    @location(5) unorm16x4 : vec4<f16>,
+    @location(6) snorm16x2 : vec2<f16>,
+    @location(7) snorm16x4 : vec4<f16>,
+    @location(8) float16x2 : vec2<f16>,
+    @location(9) float16x4 : vec4<f16>,
+    @location(10) float32 : f16,
+    @location(11) float32x2 : vec2<f16>,
+    @location(12) float32x3 : vec3<f16>,
+    @location(13) float32x4 : vec4<f16>,
+  ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    auto* expect = R"(
+enable f16;
+
+struct TintVertexData {
+  tint_vertex_data : array<u32>,
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@vertex
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var unorm8x2 : vec2<f16>;
+  var unorm8x4 : vec4<f16>;
+  var snorm8x2 : vec2<f16>;
+  var snorm8x4 : vec4<f16>;
+  var unorm16x2 : vec2<f16>;
+  var unorm16x4 : vec4<f16>;
+  var snorm16x2 : vec2<f16>;
+  var snorm16x4 : vec4<f16>;
+  var float16x2 : vec2<f16>;
+  var float16x4 : vec4<f16>;
+  var float32 : f16;
+  var float32x2 : vec2<f16>;
+  var float32x3 : vec3<f16>;
+  var float32x4 : vec4<f16>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
+    unorm8x2 = vec2<f16>(unpack4x8unorm((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u)) & 65535u)).xy);
+    unorm8x4 = vec4<f16>(unpack4x8unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))));
+    snorm8x2 = vec2<f16>(unpack4x8snorm((((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u)) & 65535u)).xy);
+    snorm8x4 = vec4<f16>(unpack4x8snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))));
+    unorm16x2 = vec2<f16>(unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))));
+    unorm16x4 = vec4<f16>(vec4<f32>(unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16unorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)))));
+    snorm16x2 = vec2<f16>(unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))));
+    snorm16x4 = vec4<f16>(vec4<f32>(unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16snorm(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)))));
+    float16x2 = vec2<f16>(unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))));
+    float16x4 = vec4<f16>(vec4<f32>(unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), unpack2x16float(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)))));
+    float32 = f16(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))));
+    float32x2 = vec2<f16>(vec2<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u)))));
+    float32x3 = vec3<f16>(vec3<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u)))));
+    float32x4 = vec4<f16>(vec4<f32>(bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 15u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] << 8u))), bitcast<f32>(((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)] >> 24u) | (tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)] << 8u)))));
+  }
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    VertexPulling::Config cfg;
+    cfg.vertex_state = {{{256,
+                          VertexStepMode::kVertex,
+                          {
+                              {VertexFormat::kUnorm8x2, 63, 0},
+                              {VertexFormat::kUnorm8x4, 63, 1},
+                              {VertexFormat::kSnorm8x2, 63, 2},
+                              {VertexFormat::kSnorm8x4, 63, 3},
+                              {VertexFormat::kUnorm16x2, 63, 4},
+                              {VertexFormat::kUnorm16x4, 63, 5},
+                              {VertexFormat::kSnorm16x2, 63, 6},
+                              {VertexFormat::kSnorm16x4, 63, 7},
+                              {VertexFormat::kFloat16x2, 63, 8},
+                              {VertexFormat::kFloat16x4, 63, 9},
+                              {VertexFormat::kFloat32, 63, 10},
+                              {VertexFormat::kFloat32x2, 63, 11},
+                              {VertexFormat::kFloat32x3, 63, 12},
+                              {VertexFormat::kFloat32x4, 63, 13},
+                          }}}};
+
+    DataMap data;
+    data.Add<VertexPulling::Config>(cfg);
+    auto got = Run<VertexPulling>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(VertexPullingTest, FormatsWithVectorsResized_Padding_SInt) {
     auto* src = R"(
 @vertex
@@ -1511,6 +1764,112 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(VertexPullingTest, FormatsWithVectorsResized_Padding_Float_F16) {
+    auto* src = R"(
+enable f16;
+
+@vertex
+fn main(
+    @location(0)  vec3_unorm8x2 :  vec3<f16>,
+    @location(1)  vec4_unorm8x2 :  vec4<f16>,
+    @location(2)  vec3_snorm8x2 :  vec3<f16>,
+    @location(3)  vec4_snorm8x2 :  vec4<f16>,
+    @location(4)  vec3_unorm16x2 : vec3<f16>,
+    @location(5)  vec4_unorm16x2 : vec4<f16>,
+    @location(6)  vec3_snorm16x2 : vec3<f16>,
+    @location(7)  vec4_snorm16x2 : vec4<f16>,
+    @location(8)  vec3_float16x2 : vec3<f16>,
+    @location(9)  vec4_float16x2 : vec4<f16>,
+    @location(10) vec2_float32 :   vec2<f16>,
+    @location(11) vec3_float32 :   vec3<f16>,
+    @location(12) vec4_float32 :   vec4<f16>,
+    @location(13) vec3_float32x2 : vec3<f16>,
+    @location(14) vec4_float32x2 : vec4<f16>,
+    @location(15) vec4_float32x3 : vec4<f16>,
+  ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    auto* expect = R"(
+enable f16;
+
+struct TintVertexData {
+  tint_vertex_data : array<u32>,
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@vertex
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var vec3_unorm8x2 : vec3<f16>;
+  var vec4_unorm8x2 : vec4<f16>;
+  var vec3_snorm8x2 : vec3<f16>;
+  var vec4_snorm8x2 : vec4<f16>;
+  var vec3_unorm16x2 : vec3<f16>;
+  var vec4_unorm16x2 : vec4<f16>;
+  var vec3_snorm16x2 : vec3<f16>;
+  var vec4_snorm16x2 : vec4<f16>;
+  var vec3_float16x2 : vec3<f16>;
+  var vec4_float16x2 : vec4<f16>;
+  var vec2_float32 : vec2<f16>;
+  var vec3_float32 : vec3<f16>;
+  var vec4_float32 : vec4<f16>;
+  var vec3_float32x2 : vec3<f16>;
+  var vec4_float32x2 : vec4<f16>;
+  var vec4_float32x3 : vec4<f16>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
+    vec3_unorm8x2 = vec3<f16>(vec2<f16>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy), 0.0);
+    vec4_unorm8x2 = vec4<f16>(vec2<f16>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy), 0.0, 1.0);
+    vec3_snorm8x2 = vec3<f16>(vec2<f16>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy), 0.0);
+    vec4_snorm8x2 = vec4<f16>(vec2<f16>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy), 0.0, 1.0);
+    vec3_unorm16x2 = vec3<f16>(vec2<f16>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0);
+    vec4_unorm16x2 = vec4<f16>(vec2<f16>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0, 1.0);
+    vec3_snorm16x2 = vec3<f16>(vec2<f16>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0);
+    vec4_snorm16x2 = vec4<f16>(vec2<f16>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0, 1.0);
+    vec3_float16x2 = vec3<f16>(vec2<f16>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0);
+    vec4_float16x2 = vec4<f16>(vec2<f16>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0, 1.0);
+    vec2_float32 = vec2<f16>(f16(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0);
+    vec3_float32 = vec3<f16>(f16(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0, 0.0);
+    vec4_float32 = vec4<f16>(f16(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])), 0.0, 0.0, 1.0);
+    vec3_float32x2 = vec3<f16>(vec2<f16>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))), 0.0);
+    vec4_float32x2 = vec4<f16>(vec2<f16>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))), 0.0, 1.0);
+    vec4_float32x3 = vec4<f16>(vec3<f16>(vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]))), 1.0);
+  }
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    VertexPulling::Config cfg;
+    cfg.vertex_state = {{{256,
+                          VertexStepMode::kVertex,
+                          {
+                              {VertexFormat::kUnorm8x2, 64, 0},
+                              {VertexFormat::kUnorm8x2, 64, 1},
+                              {VertexFormat::kSnorm8x2, 64, 2},
+                              {VertexFormat::kSnorm8x2, 64, 3},
+                              {VertexFormat::kUnorm16x2, 64, 4},
+                              {VertexFormat::kUnorm16x2, 64, 5},
+                              {VertexFormat::kSnorm16x2, 64, 6},
+                              {VertexFormat::kSnorm16x2, 64, 7},
+                              {VertexFormat::kFloat16x2, 64, 8},
+                              {VertexFormat::kFloat16x2, 64, 9},
+                              {VertexFormat::kFloat32, 64, 10},
+                              {VertexFormat::kFloat32, 64, 11},
+                              {VertexFormat::kFloat32, 64, 12},
+                              {VertexFormat::kFloat32x2, 64, 13},
+                              {VertexFormat::kFloat32x2, 64, 14},
+                              {VertexFormat::kFloat32x3, 64, 15},
+                          }}}};
+
+    DataMap data;
+    data.Add<VertexPulling::Config>(cfg);
+    auto got = Run<VertexPulling>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(VertexPullingTest, FormatsWithVectorsResized_Shrinking_SInt) {
     auto* src = R"(
 @vertex
@@ -1829,5 +2188,139 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(VertexPullingTest, FormatsWithVectorsResized_Shrinking_Float_F16) {
+    auto* src = R"(
+enable f16;
+
+@vertex
+fn main(
+    @location(0)  sclr_unorm8x2  :      f16 ,
+    @location(1)  sclr_unorm8x4  :      f16 ,
+    @location(2)  vec2_unorm8x4  : vec2<f16>,
+    @location(3)  vec3_unorm8x4  : vec3<f16>,
+    @location(4)  sclr_snorm8x2  :      f16 ,
+    @location(5)  sclr_snorm8x4  :      f16 ,
+    @location(6)  vec2_snorm8x4  : vec2<f16>,
+    @location(7)  vec3_snorm8x4  : vec3<f16>,
+    @location(8)  sclr_unorm16x2 :      f16 ,
+    @location(9)  sclr_unorm16x4 :      f16 ,
+    @location(10) vec2_unorm16x4 : vec2<f16>,
+    @location(11) vec3_unorm16x4 : vec3<f16>,
+    @location(12) sclr_snorm16x2 :      f16 ,
+    @location(13) sclr_snorm16x4 :      f16 ,
+    @location(14) vec2_snorm16x4 : vec2<f16>,
+    @location(15) vec3_snorm16x4 : vec3<f16>,
+    @location(16) sclr_float16x2 :      f16 ,
+    @location(17) sclr_float16x4 :      f16 ,
+    @location(18) vec2_float16x4 : vec2<f16>,
+    @location(19) vec3_float16x4 : vec3<f16>,
+    @location(20) sclr_float32x2 :      f16 ,
+    @location(21) sclr_float32x3 :      f16 ,
+    @location(22) vec2_float32x3 : vec2<f16>,
+    @location(23) sclr_float32x4 :      f16 ,
+    @location(24) vec2_float32x4 : vec2<f16>,
+    @location(25) vec3_float32x4 : vec3<f16>,
+  ) -> @builtin(position) vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    auto* expect = R"(
+enable f16;
+
+struct TintVertexData {
+  tint_vertex_data : array<u32>,
+}
+
+@binding(0) @group(4) var<storage, read> tint_pulling_vertex_buffer_0 : TintVertexData;
+
+@vertex
+fn main(@builtin(vertex_index) tint_pulling_vertex_index : u32) -> @builtin(position) vec4<f32> {
+  var sclr_unorm8x2 : f16;
+  var sclr_unorm8x4 : f16;
+  var vec2_unorm8x4 : vec2<f16>;
+  var vec3_unorm8x4 : vec3<f16>;
+  var sclr_snorm8x2 : f16;
+  var sclr_snorm8x4 : f16;
+  var vec2_snorm8x4 : vec2<f16>;
+  var vec3_snorm8x4 : vec3<f16>;
+  var sclr_unorm16x2 : f16;
+  var sclr_unorm16x4 : f16;
+  var vec2_unorm16x4 : vec2<f16>;
+  var vec3_unorm16x4 : vec3<f16>;
+  var sclr_snorm16x2 : f16;
+  var sclr_snorm16x4 : f16;
+  var vec2_snorm16x4 : vec2<f16>;
+  var vec3_snorm16x4 : vec3<f16>;
+  var sclr_float16x2 : f16;
+  var sclr_float16x4 : f16;
+  var vec2_float16x4 : vec2<f16>;
+  var vec3_float16x4 : vec3<f16>;
+  var sclr_float32x2 : f16;
+  var sclr_float32x3 : f16;
+  var vec2_float32x3 : vec2<f16>;
+  var sclr_float32x4 : f16;
+  var vec2_float32x4 : vec2<f16>;
+  var vec3_float32x4 : vec3<f16>;
+  {
+    let buffer_array_base_0 = (tint_pulling_vertex_index * 64u);
+    sclr_unorm8x2 = vec2<f16>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy).x;
+    sclr_unorm8x4 = vec4<f16>(unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).x;
+    vec2_unorm8x4 = vec4<f16>(unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).xy;
+    vec3_unorm8x4 = vec4<f16>(unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).xyz;
+    sclr_snorm8x2 = vec2<f16>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy).x;
+    sclr_snorm8x4 = vec4<f16>(unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).x;
+    vec2_snorm8x4 = vec4<f16>(unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).xy;
+    vec3_snorm8x4 = vec4<f16>(unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).xyz;
+    sclr_unorm16x2 = vec2<f16>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).x;
+    sclr_unorm16x4 = vec4<f16>(vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).x;
+    vec2_unorm16x4 = vec4<f16>(vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).xy;
+    vec3_unorm16x4 = vec4<f16>(vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).xyz;
+    sclr_snorm16x2 = vec2<f16>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).x;
+    sclr_snorm16x4 = vec4<f16>(vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).x;
+    vec2_snorm16x4 = vec4<f16>(vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).xy;
+    vec3_snorm16x4 = vec4<f16>(vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).xyz;
+    sclr_float16x2 = vec2<f16>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])).x;
+    sclr_float16x4 = vec4<f16>(vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).x;
+    vec2_float16x4 = vec4<f16>(vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).xy;
+    vec3_float16x4 = vec4<f16>(vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).xyz;
+    sclr_float32x2 = vec2<f16>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]))).x;
+    sclr_float32x3 = vec3<f16>(vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]))).x;
+    vec2_float32x3 = vec3<f16>(vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]))).xy;
+    sclr_float32x4 = vec4<f16>(vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]))).x;
+    vec2_float32x4 = vec4<f16>(vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]))).xy;
+    vec3_float32x4 = vec4<f16>(vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)]))).xyz;
+  }
+  return vec4<f32>(0.0, 0.0, 0.0, 1.0);
+}
+)";
+
+    VertexPulling::Config cfg;
+    cfg.vertex_state = {
+        {{256,
+          VertexStepMode::kVertex,
+          {
+              {VertexFormat::kUnorm8x2, 64, 0},   {VertexFormat::kUnorm8x4, 64, 1},
+              {VertexFormat::kUnorm8x4, 64, 2},   {VertexFormat::kUnorm8x4, 64, 3},
+              {VertexFormat::kSnorm8x2, 64, 4},   {VertexFormat::kSnorm8x4, 64, 5},
+              {VertexFormat::kSnorm8x4, 64, 6},   {VertexFormat::kSnorm8x4, 64, 7},
+              {VertexFormat::kUnorm16x2, 64, 8},  {VertexFormat::kUnorm16x4, 64, 9},
+              {VertexFormat::kUnorm16x4, 64, 10}, {VertexFormat::kUnorm16x4, 64, 11},
+              {VertexFormat::kSnorm16x2, 64, 12}, {VertexFormat::kSnorm16x4, 64, 13},
+              {VertexFormat::kSnorm16x4, 64, 14}, {VertexFormat::kSnorm16x4, 64, 15},
+              {VertexFormat::kFloat16x2, 64, 16}, {VertexFormat::kFloat16x4, 64, 17},
+              {VertexFormat::kFloat16x4, 64, 18}, {VertexFormat::kFloat16x4, 64, 19},
+              {VertexFormat::kFloat32x2, 64, 20}, {VertexFormat::kFloat32x3, 64, 21},
+              {VertexFormat::kFloat32x3, 64, 22}, {VertexFormat::kFloat32x4, 64, 23},
+              {VertexFormat::kFloat32x4, 64, 24}, {VertexFormat::kFloat32x4, 64, 25},
+          }}}};
+
+    DataMap data;
+    data.Add<VertexPulling::Config>(cfg);
+    auto got = Run<VertexPulling>(src, data);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 }  // namespace
 }  // namespace tint::transform
diff --git a/test/tint/shader_io/fragment_input_locations_f16.wgsl b/test/tint/shader_io/fragment_input_locations_f16.wgsl
new file mode 100644
index 0000000..9cb4caf
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_f16.wgsl
@@ -0,0 +1,18 @@
+enable f16;
+
+@fragment
+fn main(
+  @location(0) @interpolate(flat) loc0 : i32,
+  @location(1) @interpolate(flat) loc1 : u32,
+  @location(2) loc2 : f32,
+  @location(3) loc3 : vec4<f32>,
+  @location(4) loc4 : f16,
+  @location(5) loc5 : vec3<f16>,
+) {
+  let i : i32 = loc0;
+  let u : u32 = loc1;
+  let f : f32 = loc2;
+  let v : vec4<f32> = loc3;
+  let x : f16 = loc4;
+  let y : vec3<f16> = loc5;
+}
diff --git a/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..44d85fb
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,22 @@
+struct tint_symbol_1 {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+
+void main_inner(int loc0, uint loc1, float loc2, float4 loc3, float16_t loc4, vector<float16_t, 3> loc5) {
+  const int i = loc0;
+  const uint u = loc1;
+  const float f = loc2;
+  const float4 v = loc3;
+  const float16_t x = loc4;
+  const vector<float16_t, 3> y = loc5;
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5);
+  return;
+}
diff --git a/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..faa74b5
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+struct tint_symbol_1 {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+
+void main_inner(int loc0, uint loc1, float loc2, float4 loc3, float16_t loc4, vector<float16_t, 3> loc5) {
+  const int i = loc0;
+  const uint u = loc1;
+  const float f = loc2;
+  const float4 v = loc3;
+  const float16_t x = loc4;
+  const vector<float16_t, 3> y = loc5;
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5);
+  return;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x0000020BD1D24AC0(6,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..514ba55
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 0) flat in int loc0_1;
+layout(location = 1) flat in uint loc1_1;
+layout(location = 2) in float loc2_1;
+layout(location = 3) in vec4 loc3_1;
+layout(location = 4) in float16_t loc4_1;
+layout(location = 5) in f16vec3 loc5_1;
+void tint_symbol(int loc0, uint loc1, float loc2, vec4 loc3, float16_t loc4, f16vec3 loc5) {
+  int i = loc0;
+  uint u = loc1;
+  float f = loc2;
+  vec4 v = loc3;
+  float16_t x = loc4;
+  f16vec3 y = loc5;
+}
+
+void main() {
+  tint_symbol(loc0_1, loc1_1, loc2_1, loc3_1, loc4_1, loc5_1);
+  return;
+}
diff --git a/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.msl b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.msl
new file mode 100644
index 0000000..6399fd7
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.msl
@@ -0,0 +1,26 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_2 {
+  int loc0 [[user(locn0)]] [[flat]];
+  uint loc1 [[user(locn1)]] [[flat]];
+  float loc2 [[user(locn2)]];
+  float4 loc3 [[user(locn3)]];
+  half loc4 [[user(locn4)]];
+  half3 loc5 [[user(locn5)]];
+};
+
+void tint_symbol_inner(int loc0, uint loc1, float loc2, float4 loc3, half loc4, half3 loc5) {
+  int const i = loc0;
+  uint const u = loc1;
+  float const f = loc2;
+  float4 const v = loc3;
+  half const x = loc4;
+  half3 const y = loc5;
+}
+
+fragment void tint_symbol(tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  tint_symbol_inner(tint_symbol_1.loc0, tint_symbol_1.loc1, tint_symbol_1.loc2, tint_symbol_1.loc3, tint_symbol_1.loc4, tint_symbol_1.loc5);
+  return;
+}
+
diff --git a/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..761fb61
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.spvasm
@@ -0,0 +1,77 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 39
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %loc0_1 %loc1_1 %loc2_1 %loc3_1 %loc4_1 %loc5_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %loc0_1 "loc0_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %main_inner "main_inner"
+               OpName %loc0 "loc0"
+               OpName %loc1 "loc1"
+               OpName %loc2 "loc2"
+               OpName %loc3 "loc3"
+               OpName %loc4 "loc4"
+               OpName %loc5 "loc5"
+               OpName %main "main"
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc0_1 Flat
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc1_1 Flat
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %loc5_1 Location 5
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+     %loc0_1 = OpVariable %_ptr_Input_int Input
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+     %loc1_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+     %loc2_1 = OpVariable %_ptr_Input_float Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+     %loc3_1 = OpVariable %_ptr_Input_v4float Input
+       %half = OpTypeFloat 16
+%_ptr_Input_half = OpTypePointer Input %half
+     %loc4_1 = OpVariable %_ptr_Input_half Input
+     %v3half = OpTypeVector %half 3
+%_ptr_Input_v3half = OpTypePointer Input %v3half
+     %loc5_1 = OpVariable %_ptr_Input_v3half Input
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void %int %uint %float %v4float %half %v3half
+         %29 = OpTypeFunction %void
+ %main_inner = OpFunction %void None %19
+       %loc0 = OpFunctionParameter %int
+       %loc1 = OpFunctionParameter %uint
+       %loc2 = OpFunctionParameter %float
+       %loc3 = OpFunctionParameter %v4float
+       %loc4 = OpFunctionParameter %half
+       %loc5 = OpFunctionParameter %v3half
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %29
+         %31 = OpLabel
+         %33 = OpLoad %int %loc0_1
+         %34 = OpLoad %uint %loc1_1
+         %35 = OpLoad %float %loc2_1
+         %36 = OpLoad %v4float %loc3_1
+         %37 = OpLoad %half %loc4_1
+         %38 = OpLoad %v3half %loc5_1
+         %32 = OpFunctionCall %void %main_inner %33 %34 %35 %36 %37 %38
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..9be6ff7
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_f16.wgsl.expected.wgsl
@@ -0,0 +1,11 @@
+enable f16;
+
+@fragment
+fn main(@location(0) @interpolate(flat) loc0 : i32, @location(1) @interpolate(flat) loc1 : u32, @location(2) loc2 : f32, @location(3) loc3 : vec4<f32>, @location(4) loc4 : f16, @location(5) loc5 : vec3<f16>) {
+  let i : i32 = loc0;
+  let u : u32 = loc1;
+  let f : f32 = loc2;
+  let v : vec4<f32> = loc3;
+  let x : f16 = loc4;
+  let y : vec3<f16> = loc5;
+}
diff --git a/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl
new file mode 100644
index 0000000..e89a938
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl
@@ -0,0 +1,20 @@
+enable f16;
+
+struct FragmentInputs {
+  @location(0) @interpolate(flat) loc0 : i32,
+  @location(1) @interpolate(flat) loc1 : u32,
+  @location(2) loc2 : f32,
+  @location(3) loc3 : vec4<f32>,
+  @location(4) loc4 : f16,
+  @location(5) loc5 : vec3<f16>,
+};
+
+@fragment
+fn main(inputs : FragmentInputs) {
+  let i : i32 = inputs.loc0;
+  let u : u32 = inputs.loc1;
+  let f : f32 = inputs.loc2;
+  let v : vec4<f32> = inputs.loc3;
+  let x : f16 = inputs.loc4;
+  let y : vec3<f16> = inputs.loc5;
+}
diff --git a/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..15a56ef
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,31 @@
+struct FragmentInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol_1 {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+
+void main_inner(FragmentInputs inputs) {
+  const int i = inputs.loc0;
+  const uint u = inputs.loc1;
+  const float f = inputs.loc2;
+  const float4 v = inputs.loc3;
+  const float16_t x = inputs.loc4;
+  const vector<float16_t, 3> y = inputs.loc5;
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  const FragmentInputs tint_symbol_2 = {tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5};
+  main_inner(tint_symbol_2);
+  return;
+}
diff --git a/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..d3aa003
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,36 @@
+SKIP: FAILED
+
+struct FragmentInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol_1 {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+
+void main_inner(FragmentInputs inputs) {
+  const int i = inputs.loc0;
+  const uint u = inputs.loc1;
+  const float f = inputs.loc2;
+  const float4 v = inputs.loc3;
+  const float16_t x = inputs.loc4;
+  const vector<float16_t, 3> y = inputs.loc5;
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  const FragmentInputs tint_symbol_2 = {tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5};
+  main_inner(tint_symbol_2);
+  return;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x000001A7231510D0(6,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..30e9734
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.glsl
@@ -0,0 +1,33 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 0) flat in int loc0_1;
+layout(location = 1) flat in uint loc1_1;
+layout(location = 2) in float loc2_1;
+layout(location = 3) in vec4 loc3_1;
+layout(location = 4) in float16_t loc4_1;
+layout(location = 5) in f16vec3 loc5_1;
+struct FragmentInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  vec4 loc3;
+  float16_t loc4;
+  f16vec3 loc5;
+};
+
+void tint_symbol(FragmentInputs inputs) {
+  int i = inputs.loc0;
+  uint u = inputs.loc1;
+  float f = inputs.loc2;
+  vec4 v = inputs.loc3;
+  float16_t x = inputs.loc4;
+  f16vec3 y = inputs.loc5;
+}
+
+void main() {
+  FragmentInputs tint_symbol_1 = FragmentInputs(loc0_1, loc1_1, loc2_1, loc3_1, loc4_1, loc5_1);
+  tint_symbol(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.msl b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.msl
new file mode 100644
index 0000000..0e8299d
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.msl
@@ -0,0 +1,36 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct FragmentInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  half loc4;
+  half3 loc5;
+};
+
+struct tint_symbol_2 {
+  int loc0 [[user(locn0)]] [[flat]];
+  uint loc1 [[user(locn1)]] [[flat]];
+  float loc2 [[user(locn2)]];
+  float4 loc3 [[user(locn3)]];
+  half loc4 [[user(locn4)]];
+  half3 loc5 [[user(locn5)]];
+};
+
+void tint_symbol_inner(FragmentInputs inputs) {
+  int const i = inputs.loc0;
+  uint const u = inputs.loc1;
+  float const f = inputs.loc2;
+  float4 const v = inputs.loc3;
+  half const x = inputs.loc4;
+  half3 const y = inputs.loc5;
+}
+
+fragment void tint_symbol(tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  FragmentInputs const tint_symbol_3 = {.loc0=tint_symbol_1.loc0, .loc1=tint_symbol_1.loc1, .loc2=tint_symbol_1.loc2, .loc3=tint_symbol_1.loc3, .loc4=tint_symbol_1.loc4, .loc5=tint_symbol_1.loc5};
+  tint_symbol_inner(tint_symbol_3);
+  return;
+}
+
diff --git a/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..b7af99f
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.spvasm
@@ -0,0 +1,88 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 42
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %loc0_1 %loc1_1 %loc2_1 %loc3_1 %loc4_1 %loc5_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %loc0_1 "loc0_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %FragmentInputs "FragmentInputs"
+               OpMemberName %FragmentInputs 0 "loc0"
+               OpMemberName %FragmentInputs 1 "loc1"
+               OpMemberName %FragmentInputs 2 "loc2"
+               OpMemberName %FragmentInputs 3 "loc3"
+               OpMemberName %FragmentInputs 4 "loc4"
+               OpMemberName %FragmentInputs 5 "loc5"
+               OpName %main_inner "main_inner"
+               OpName %inputs "inputs"
+               OpName %main "main"
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc0_1 Flat
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc1_1 Flat
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %loc5_1 Location 5
+               OpMemberDecorate %FragmentInputs 0 Offset 0
+               OpMemberDecorate %FragmentInputs 1 Offset 4
+               OpMemberDecorate %FragmentInputs 2 Offset 8
+               OpMemberDecorate %FragmentInputs 3 Offset 16
+               OpMemberDecorate %FragmentInputs 4 Offset 32
+               OpMemberDecorate %FragmentInputs 5 Offset 40
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+     %loc0_1 = OpVariable %_ptr_Input_int Input
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+     %loc1_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+     %loc2_1 = OpVariable %_ptr_Input_float Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+     %loc3_1 = OpVariable %_ptr_Input_v4float Input
+       %half = OpTypeFloat 16
+%_ptr_Input_half = OpTypePointer Input %half
+     %loc4_1 = OpVariable %_ptr_Input_half Input
+     %v3half = OpTypeVector %half 3
+%_ptr_Input_v3half = OpTypePointer Input %v3half
+     %loc5_1 = OpVariable %_ptr_Input_v3half Input
+       %void = OpTypeVoid
+%FragmentInputs = OpTypeStruct %int %uint %float %v4float %half %v3half
+         %19 = OpTypeFunction %void %FragmentInputs
+         %31 = OpTypeFunction %void
+ %main_inner = OpFunction %void None %19
+     %inputs = OpFunctionParameter %FragmentInputs
+         %24 = OpLabel
+         %25 = OpCompositeExtract %int %inputs 0
+         %26 = OpCompositeExtract %uint %inputs 1
+         %27 = OpCompositeExtract %float %inputs 2
+         %28 = OpCompositeExtract %v4float %inputs 3
+         %29 = OpCompositeExtract %half %inputs 4
+         %30 = OpCompositeExtract %v3half %inputs 5
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %31
+         %33 = OpLabel
+         %35 = OpLoad %int %loc0_1
+         %36 = OpLoad %uint %loc1_1
+         %37 = OpLoad %float %loc2_1
+         %38 = OpLoad %v4float %loc3_1
+         %39 = OpLoad %half %loc4_1
+         %40 = OpLoad %v3half %loc5_1
+         %41 = OpCompositeConstruct %FragmentInputs %35 %36 %37 %38 %39 %40
+         %34 = OpFunctionCall %void %main_inner %41
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..1f267cf
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_locations_struct_f16.wgsl.expected.wgsl
@@ -0,0 +1,26 @@
+enable f16;
+
+struct FragmentInputs {
+  @location(0) @interpolate(flat)
+  loc0 : i32,
+  @location(1) @interpolate(flat)
+  loc1 : u32,
+  @location(2)
+  loc2 : f32,
+  @location(3)
+  loc3 : vec4<f32>,
+  @location(4)
+  loc4 : f16,
+  @location(5)
+  loc5 : vec3<f16>,
+}
+
+@fragment
+fn main(inputs : FragmentInputs) {
+  let i : i32 = inputs.loc0;
+  let u : u32 = inputs.loc1;
+  let f : f32 = inputs.loc2;
+  let v : vec4<f32> = inputs.loc3;
+  let x : f16 = inputs.loc4;
+  let y : vec3<f16> = inputs.loc5;
+}
diff --git a/test/tint/shader_io/fragment_input_mixed_f16.wgsl b/test/tint/shader_io/fragment_input_mixed_f16.wgsl
new file mode 100644
index 0000000..3eb88b1
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_mixed_f16.wgsl
@@ -0,0 +1,33 @@
+enable f16;
+
+struct FragmentInputs0 {
+  @builtin(position) position : vec4<f32>,
+  @location(0) @interpolate(flat) loc0 : i32,
+};
+struct FragmentInputs1 {
+  @location(3) loc3 : vec4<f32>,
+  @location(5) loc5 : vec3<f16>,
+  @builtin(sample_mask) sample_mask : u32,
+};
+
+@fragment
+fn main(
+  inputs0 : FragmentInputs0,
+  @builtin(front_facing) front_facing : bool,
+  @location(1) @interpolate(flat) loc1 : u32,
+  @builtin(sample_index) sample_index : u32,
+  inputs1 : FragmentInputs1,
+  @location(2) loc2 : f32,
+  @location(4) loc4 : f16,
+) {
+  if (front_facing) {
+    let foo : vec4<f32> = inputs0.position;
+    let bar : u32 = sample_index + inputs1.sample_mask;
+    let i : i32 = inputs0.loc0;
+    let u : u32 = loc1;
+    let f : f32 = loc2;
+    let v : vec4<f32> = inputs1.loc3;
+    let x : f16 = loc4;
+    let y : vec3<f16> = inputs1.loc5;
+  }
+}
diff --git a/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..f8de307
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,41 @@
+struct FragmentInputs0 {
+  float4 position;
+  int loc0;
+};
+struct FragmentInputs1 {
+  float4 loc3;
+  vector<float16_t, 3> loc5;
+  uint sample_mask;
+};
+struct tint_symbol_1 {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+  float4 position : SV_Position;
+  bool front_facing : SV_IsFrontFace;
+  uint sample_index : SV_SampleIndex;
+  uint sample_mask : SV_Coverage;
+};
+
+void main_inner(FragmentInputs0 inputs0, bool front_facing, uint loc1, uint sample_index, FragmentInputs1 inputs1, float loc2, float16_t loc4) {
+  if (front_facing) {
+    const float4 foo = inputs0.position;
+    const uint bar = (sample_index + inputs1.sample_mask);
+    const int i = inputs0.loc0;
+    const uint u = loc1;
+    const float f = loc2;
+    const float4 v = inputs1.loc3;
+    const float16_t x = loc4;
+    const vector<float16_t, 3> y = inputs1.loc5;
+  }
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  const FragmentInputs0 tint_symbol_2 = {tint_symbol.position, tint_symbol.loc0};
+  const FragmentInputs1 tint_symbol_3 = {tint_symbol.loc3, tint_symbol.loc5, tint_symbol.sample_mask};
+  main_inner(tint_symbol_2, tint_symbol.front_facing, tint_symbol.loc1, tint_symbol.sample_index, tint_symbol_3, tint_symbol.loc2, tint_symbol.loc4);
+  return;
+}
diff --git a/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..e1f2d63
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,46 @@
+SKIP: FAILED
+
+struct FragmentInputs0 {
+  float4 position;
+  int loc0;
+};
+struct FragmentInputs1 {
+  float4 loc3;
+  vector<float16_t, 3> loc5;
+  uint sample_mask;
+};
+struct tint_symbol_1 {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+  float4 position : SV_Position;
+  bool front_facing : SV_IsFrontFace;
+  uint sample_index : SV_SampleIndex;
+  uint sample_mask : SV_Coverage;
+};
+
+void main_inner(FragmentInputs0 inputs0, bool front_facing, uint loc1, uint sample_index, FragmentInputs1 inputs1, float loc2, float16_t loc4) {
+  if (front_facing) {
+    const float4 foo = inputs0.position;
+    const uint bar = (sample_index + inputs1.sample_mask);
+    const int i = inputs0.loc0;
+    const uint u = loc1;
+    const float f = loc2;
+    const float4 v = inputs1.loc3;
+    const float16_t x = loc4;
+    const vector<float16_t, 3> y = inputs1.loc5;
+  }
+}
+
+void main(tint_symbol_1 tint_symbol) {
+  const FragmentInputs0 tint_symbol_2 = {tint_symbol.position, tint_symbol.loc0};
+  const FragmentInputs1 tint_symbol_3 = {tint_symbol.loc3, tint_symbol.loc5, tint_symbol.sample_mask};
+  main_inner(tint_symbol_2, tint_symbol.front_facing, tint_symbol.loc1, tint_symbol.sample_index, tint_symbol_3, tint_symbol.loc2, tint_symbol.loc4);
+  return;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x000001E6355E01A0(7,10-18): error X3000: syntax error: unexpected token 'float16_t'
+
diff --git a/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.glsl b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..2226a6a
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.glsl
@@ -0,0 +1,41 @@
+#version 310 es
+#extension GL_OES_sample_variables : require
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 0) flat in int loc0_1;
+layout(location = 1) flat in uint loc1_1;
+layout(location = 3) in vec4 loc3_1;
+layout(location = 5) in f16vec3 loc5_1;
+layout(location = 2) in float loc2_1;
+layout(location = 4) in float16_t loc4_1;
+struct FragmentInputs0 {
+  vec4 position;
+  int loc0;
+};
+
+struct FragmentInputs1 {
+  vec4 loc3;
+  f16vec3 loc5;
+  uint sample_mask;
+};
+
+void tint_symbol(FragmentInputs0 inputs0, bool front_facing, uint loc1, uint sample_index, FragmentInputs1 inputs1, float loc2, float16_t loc4) {
+  if (front_facing) {
+    vec4 foo = inputs0.position;
+    uint bar = (sample_index + inputs1.sample_mask);
+    int i = inputs0.loc0;
+    uint u = loc1;
+    float f = loc2;
+    vec4 v = inputs1.loc3;
+    float16_t x = loc4;
+    f16vec3 y = inputs1.loc5;
+  }
+}
+
+void main() {
+  FragmentInputs0 tint_symbol_1 = FragmentInputs0(gl_FragCoord, loc0_1);
+  FragmentInputs1 tint_symbol_2 = FragmentInputs1(loc3_1, loc5_1, uint(gl_SampleMaskIn[0]));
+  tint_symbol(tint_symbol_1, gl_FrontFacing, loc1_1, uint(gl_SampleID), tint_symbol_2, loc2_1, loc4_1);
+  return;
+}
diff --git a/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.msl b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.msl
new file mode 100644
index 0000000..fc4df0d
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.msl
@@ -0,0 +1,43 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct FragmentInputs0 {
+  float4 position;
+  int loc0;
+};
+
+struct FragmentInputs1 {
+  float4 loc3;
+  half3 loc5;
+  uint sample_mask;
+};
+
+struct tint_symbol_2 {
+  int loc0 [[user(locn0)]] [[flat]];
+  uint loc1 [[user(locn1)]] [[flat]];
+  float loc2 [[user(locn2)]];
+  float4 loc3 [[user(locn3)]];
+  half loc4 [[user(locn4)]];
+  half3 loc5 [[user(locn5)]];
+};
+
+void tint_symbol_inner(FragmentInputs0 inputs0, bool front_facing, uint loc1, uint sample_index, FragmentInputs1 inputs1, float loc2, half loc4) {
+  if (front_facing) {
+    float4 const foo = inputs0.position;
+    uint const bar = (sample_index + inputs1.sample_mask);
+    int const i = inputs0.loc0;
+    uint const u = loc1;
+    float const f = loc2;
+    float4 const v = inputs1.loc3;
+    half const x = loc4;
+    half3 const y = inputs1.loc5;
+  }
+}
+
+fragment void tint_symbol(float4 position [[position]], bool front_facing [[front_facing]], uint sample_index [[sample_id]], uint sample_mask [[sample_mask]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  FragmentInputs0 const tint_symbol_3 = {.position=position, .loc0=tint_symbol_1.loc0};
+  FragmentInputs1 const tint_symbol_4 = {.loc3=tint_symbol_1.loc3, .loc5=tint_symbol_1.loc5, .sample_mask=sample_mask};
+  tint_symbol_inner(tint_symbol_3, front_facing, tint_symbol_1.loc1, sample_index, tint_symbol_4, tint_symbol_1.loc2, tint_symbol_1.loc4);
+  return;
+}
+
diff --git a/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.spvasm b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..b565b14
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.spvasm
@@ -0,0 +1,133 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 67
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpCapability SampleRateShading
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %position_1 %loc0_1 %front_facing_1 %loc1_1 %sample_index_1 %loc3_1 %loc5_1 %sample_mask_1 %loc2_1 %loc4_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %position_1 "position_1"
+               OpName %loc0_1 "loc0_1"
+               OpName %front_facing_1 "front_facing_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %sample_index_1 "sample_index_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %sample_mask_1 "sample_mask_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %FragmentInputs0 "FragmentInputs0"
+               OpMemberName %FragmentInputs0 0 "position"
+               OpMemberName %FragmentInputs0 1 "loc0"
+               OpName %FragmentInputs1 "FragmentInputs1"
+               OpMemberName %FragmentInputs1 0 "loc3"
+               OpMemberName %FragmentInputs1 1 "loc5"
+               OpMemberName %FragmentInputs1 2 "sample_mask"
+               OpName %main_inner "main_inner"
+               OpName %inputs0 "inputs0"
+               OpName %front_facing "front_facing"
+               OpName %loc1 "loc1"
+               OpName %sample_index "sample_index"
+               OpName %inputs1 "inputs1"
+               OpName %loc2 "loc2"
+               OpName %loc4 "loc4"
+               OpName %main "main"
+               OpDecorate %position_1 BuiltIn FragCoord
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc0_1 Flat
+               OpDecorate %front_facing_1 BuiltIn FrontFacing
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc1_1 Flat
+               OpDecorate %sample_index_1 BuiltIn SampleId
+               OpDecorate %sample_index_1 Flat
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc5_1 Location 5
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+               OpDecorate %sample_mask_1 BuiltIn SampleMask
+               OpDecorate %sample_mask_1 Flat
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc4_1 Location 4
+               OpMemberDecorate %FragmentInputs0 0 Offset 0
+               OpMemberDecorate %FragmentInputs0 1 Offset 16
+               OpMemberDecorate %FragmentInputs1 0 Offset 0
+               OpMemberDecorate %FragmentInputs1 1 Offset 16
+               OpMemberDecorate %FragmentInputs1 2 Offset 24
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %position_1 = OpVariable %_ptr_Input_v4float Input
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+     %loc0_1 = OpVariable %_ptr_Input_int Input
+       %bool = OpTypeBool
+%_ptr_Input_bool = OpTypePointer Input %bool
+%front_facing_1 = OpVariable %_ptr_Input_bool Input
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+     %loc1_1 = OpVariable %_ptr_Input_uint Input
+%sample_index_1 = OpVariable %_ptr_Input_uint Input
+     %loc3_1 = OpVariable %_ptr_Input_v4float Input
+       %half = OpTypeFloat 16
+     %v3half = OpTypeVector %half 3
+%_ptr_Input_v3half = OpTypePointer Input %v3half
+     %loc5_1 = OpVariable %_ptr_Input_v3half Input
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+%_ptr_Input__arr_uint_uint_1 = OpTypePointer Input %_arr_uint_uint_1
+%sample_mask_1 = OpVariable %_ptr_Input__arr_uint_uint_1 Input
+%_ptr_Input_float = OpTypePointer Input %float
+     %loc2_1 = OpVariable %_ptr_Input_float Input
+%_ptr_Input_half = OpTypePointer Input %half
+     %loc4_1 = OpVariable %_ptr_Input_half Input
+       %void = OpTypeVoid
+%FragmentInputs0 = OpTypeStruct %v4float %int
+%FragmentInputs1 = OpTypeStruct %v4float %v3half %uint
+         %28 = OpTypeFunction %void %FragmentInputs0 %bool %uint %uint %FragmentInputs1 %float %half
+         %49 = OpTypeFunction %void
+         %61 = OpConstantNull %int
+ %main_inner = OpFunction %void None %28
+    %inputs0 = OpFunctionParameter %FragmentInputs0
+%front_facing = OpFunctionParameter %bool
+       %loc1 = OpFunctionParameter %uint
+%sample_index = OpFunctionParameter %uint
+    %inputs1 = OpFunctionParameter %FragmentInputs1
+       %loc2 = OpFunctionParameter %float
+       %loc4 = OpFunctionParameter %half
+         %40 = OpLabel
+               OpSelectionMerge %41 None
+               OpBranchConditional %front_facing %42 %41
+         %42 = OpLabel
+         %43 = OpCompositeExtract %v4float %inputs0 0
+         %44 = OpCompositeExtract %uint %inputs1 2
+         %45 = OpIAdd %uint %sample_index %44
+         %46 = OpCompositeExtract %int %inputs0 1
+         %47 = OpCompositeExtract %v4float %inputs1 0
+         %48 = OpCompositeExtract %v3half %inputs1 1
+               OpBranch %41
+         %41 = OpLabel
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %49
+         %51 = OpLabel
+         %53 = OpLoad %v4float %position_1
+         %54 = OpLoad %int %loc0_1
+         %55 = OpCompositeConstruct %FragmentInputs0 %53 %54
+         %56 = OpLoad %bool %front_facing_1
+         %57 = OpLoad %uint %loc1_1
+         %58 = OpLoad %uint %sample_index_1
+         %59 = OpLoad %v4float %loc3_1
+         %60 = OpLoad %v3half %loc5_1
+         %62 = OpAccessChain %_ptr_Input_uint %sample_mask_1 %61
+         %63 = OpLoad %uint %62
+         %64 = OpCompositeConstruct %FragmentInputs1 %59 %60 %63
+         %65 = OpLoad %float %loc2_1
+         %66 = OpLoad %half %loc4_1
+         %52 = OpFunctionCall %void %main_inner %55 %56 %57 %58 %64 %65 %66
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.wgsl b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..c446f71
--- /dev/null
+++ b/test/tint/shader_io/fragment_input_mixed_f16.wgsl.expected.wgsl
@@ -0,0 +1,31 @@
+enable f16;
+
+struct FragmentInputs0 {
+  @builtin(position)
+  position : vec4<f32>,
+  @location(0) @interpolate(flat)
+  loc0 : i32,
+}
+
+struct FragmentInputs1 {
+  @location(3)
+  loc3 : vec4<f32>,
+  @location(5)
+  loc5 : vec3<f16>,
+  @builtin(sample_mask)
+  sample_mask : u32,
+}
+
+@fragment
+fn main(inputs0 : FragmentInputs0, @builtin(front_facing) front_facing : bool, @location(1) @interpolate(flat) loc1 : u32, @builtin(sample_index) sample_index : u32, inputs1 : FragmentInputs1, @location(2) loc2 : f32, @location(4) loc4 : f16) {
+  if (front_facing) {
+    let foo : vec4<f32> = inputs0.position;
+    let bar : u32 = (sample_index + inputs1.sample_mask);
+    let i : i32 = inputs0.loc0;
+    let u : u32 = loc1;
+    let f : f32 = loc2;
+    let v : vec4<f32> = inputs1.loc3;
+    let x : f16 = loc4;
+    let y : vec3<f16> = inputs1.loc5;
+  }
+}
diff --git a/test/tint/shader_io/fragment_output_locations_f16.wgsl b/test/tint/shader_io/fragment_output_locations_f16.wgsl
new file mode 100644
index 0000000..62f7392
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_f16.wgsl
@@ -0,0 +1,31 @@
+enable f16;
+
+@fragment
+fn main0() -> @location(0) i32 {
+  return 1;
+}
+
+@fragment
+fn main1() -> @location(1) u32 {
+  return 1u;
+}
+
+@fragment
+fn main2() -> @location(2) f32 {
+  return 1.0;
+}
+
+@fragment
+fn main3() -> @location(3) vec4<f32> {
+  return vec4<f32>(1.0, 2.0, 3.0, 4.0);
+}
+
+@fragment
+fn main4() -> @location(4) f16 {
+  return 2.25h;
+}
+
+@fragment
+fn main5() -> @location(5) vec3<f16> {
+  return vec3<f16>(3.0h, 5.0h, 8.0h);
+}
diff --git a/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..d8826de
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,89 @@
+struct tint_symbol {
+  int value : SV_Target0;
+};
+
+int main0_inner() {
+  return 1;
+}
+
+tint_symbol main0() {
+  const int inner_result = main0_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  uint value : SV_Target1;
+};
+
+uint main1_inner() {
+  return 1u;
+}
+
+tint_symbol_1 main1() {
+  const uint inner_result_1 = main1_inner();
+  tint_symbol_1 wrapper_result_1 = (tint_symbol_1)0;
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+
+struct tint_symbol_2 {
+  float value : SV_Target2;
+};
+
+float main2_inner() {
+  return 1.0f;
+}
+
+tint_symbol_2 main2() {
+  const float inner_result_2 = main2_inner();
+  tint_symbol_2 wrapper_result_2 = (tint_symbol_2)0;
+  wrapper_result_2.value = inner_result_2;
+  return wrapper_result_2;
+}
+
+struct tint_symbol_3 {
+  float4 value : SV_Target3;
+};
+
+float4 main3_inner() {
+  return float4(1.0f, 2.0f, 3.0f, 4.0f);
+}
+
+tint_symbol_3 main3() {
+  const float4 inner_result_3 = main3_inner();
+  tint_symbol_3 wrapper_result_3 = (tint_symbol_3)0;
+  wrapper_result_3.value = inner_result_3;
+  return wrapper_result_3;
+}
+
+struct tint_symbol_4 {
+  float16_t value : SV_Target4;
+};
+
+float16_t main4_inner() {
+  return float16_t(2.25h);
+}
+
+tint_symbol_4 main4() {
+  const float16_t inner_result_4 = main4_inner();
+  tint_symbol_4 wrapper_result_4 = (tint_symbol_4)0;
+  wrapper_result_4.value = inner_result_4;
+  return wrapper_result_4;
+}
+
+struct tint_symbol_5 {
+  vector<float16_t, 3> value : SV_Target5;
+};
+
+vector<float16_t, 3> main5_inner() {
+  return vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h));
+}
+
+tint_symbol_5 main5() {
+  const vector<float16_t, 3> inner_result_5 = main5_inner();
+  tint_symbol_5 wrapper_result_5 = (tint_symbol_5)0;
+  wrapper_result_5.value = inner_result_5;
+  return wrapper_result_5;
+}
diff --git a/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..6172c1c
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,94 @@
+SKIP: FAILED
+
+struct tint_symbol {
+  int value : SV_Target0;
+};
+
+int main0_inner() {
+  return 1;
+}
+
+tint_symbol main0() {
+  const int inner_result = main0_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  uint value : SV_Target1;
+};
+
+uint main1_inner() {
+  return 1u;
+}
+
+tint_symbol_1 main1() {
+  const uint inner_result_1 = main1_inner();
+  tint_symbol_1 wrapper_result_1 = (tint_symbol_1)0;
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+
+struct tint_symbol_2 {
+  float value : SV_Target2;
+};
+
+float main2_inner() {
+  return 1.0f;
+}
+
+tint_symbol_2 main2() {
+  const float inner_result_2 = main2_inner();
+  tint_symbol_2 wrapper_result_2 = (tint_symbol_2)0;
+  wrapper_result_2.value = inner_result_2;
+  return wrapper_result_2;
+}
+
+struct tint_symbol_3 {
+  float4 value : SV_Target3;
+};
+
+float4 main3_inner() {
+  return float4(1.0f, 2.0f, 3.0f, 4.0f);
+}
+
+tint_symbol_3 main3() {
+  const float4 inner_result_3 = main3_inner();
+  tint_symbol_3 wrapper_result_3 = (tint_symbol_3)0;
+  wrapper_result_3.value = inner_result_3;
+  return wrapper_result_3;
+}
+
+struct tint_symbol_4 {
+  float16_t value : SV_Target4;
+};
+
+float16_t main4_inner() {
+  return float16_t(2.25h);
+}
+
+tint_symbol_4 main4() {
+  const float16_t inner_result_4 = main4_inner();
+  tint_symbol_4 wrapper_result_4 = (tint_symbol_4)0;
+  wrapper_result_4.value = inner_result_4;
+  return wrapper_result_4;
+}
+
+struct tint_symbol_5 {
+  vector<float16_t, 3> value : SV_Target5;
+};
+
+vector<float16_t, 3> main5_inner() {
+  return vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h));
+}
+
+tint_symbol_5 main5() {
+  const vector<float16_t, 3> inner_result_5 = main5_inner();
+  tint_symbol_5 wrapper_result_5 = (tint_symbol_5)0;
+  wrapper_result_5.value = inner_result_5;
+  return wrapper_result_5;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x0000023E506E30B0(62,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..1ecbe7d
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.glsl
@@ -0,0 +1,84 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 0) out int value;
+int main0() {
+  return 1;
+}
+
+void main() {
+  int inner_result = main0();
+  value = inner_result;
+  return;
+}
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 1) out uint value;
+uint main1() {
+  return 1u;
+}
+
+void main() {
+  uint inner_result = main1();
+  value = inner_result;
+  return;
+}
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 2) out float value;
+float main2() {
+  return 1.0f;
+}
+
+void main() {
+  float inner_result = main2();
+  value = inner_result;
+  return;
+}
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 3) out vec4 value;
+vec4 main3() {
+  return vec4(1.0f, 2.0f, 3.0f, 4.0f);
+}
+
+void main() {
+  vec4 inner_result = main3();
+  value = inner_result;
+  return;
+}
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 4) out float16_t value;
+float16_t main4() {
+  return 2.25hf;
+}
+
+void main() {
+  float16_t inner_result = main4();
+  value = inner_result;
+  return;
+}
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 5) out f16vec3 value;
+f16vec3 main5() {
+  return f16vec3(3.0hf, 5.0hf, 8.0hf);
+}
+
+void main() {
+  f16vec3 inner_result = main5();
+  value = inner_result;
+  return;
+}
diff --git a/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.msl b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.msl
new file mode 100644
index 0000000..803717b
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.msl
@@ -0,0 +1,93 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol {
+  int value [[color(0)]];
+};
+
+int main0_inner() {
+  return 1;
+}
+
+fragment tint_symbol main0() {
+  int const inner_result = main0_inner();
+  tint_symbol wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+struct tint_symbol_1 {
+  uint value [[color(1)]];
+};
+
+uint main1_inner() {
+  return 1u;
+}
+
+fragment tint_symbol_1 main1() {
+  uint const inner_result_1 = main1_inner();
+  tint_symbol_1 wrapper_result_1 = {};
+  wrapper_result_1.value = inner_result_1;
+  return wrapper_result_1;
+}
+
+struct tint_symbol_2 {
+  float value [[color(2)]];
+};
+
+float main2_inner() {
+  return 1.0f;
+}
+
+fragment tint_symbol_2 main2() {
+  float const inner_result_2 = main2_inner();
+  tint_symbol_2 wrapper_result_2 = {};
+  wrapper_result_2.value = inner_result_2;
+  return wrapper_result_2;
+}
+
+struct tint_symbol_3 {
+  float4 value [[color(3)]];
+};
+
+float4 main3_inner() {
+  return float4(1.0f, 2.0f, 3.0f, 4.0f);
+}
+
+fragment tint_symbol_3 main3() {
+  float4 const inner_result_3 = main3_inner();
+  tint_symbol_3 wrapper_result_3 = {};
+  wrapper_result_3.value = inner_result_3;
+  return wrapper_result_3;
+}
+
+struct tint_symbol_4 {
+  half value [[color(4)]];
+};
+
+half main4_inner() {
+  return 2.25h;
+}
+
+fragment tint_symbol_4 main4() {
+  half const inner_result_4 = main4_inner();
+  tint_symbol_4 wrapper_result_4 = {};
+  wrapper_result_4.value = inner_result_4;
+  return wrapper_result_4;
+}
+
+struct tint_symbol_5 {
+  half3 value [[color(5)]];
+};
+
+half3 main5_inner() {
+  return half3(3.0h, 5.0h, 8.0h);
+}
+
+fragment tint_symbol_5 main5() {
+  half3 const inner_result_5 = main5_inner();
+  tint_symbol_5 wrapper_result_5 = {};
+  wrapper_result_5.value = inner_result_5;
+  return wrapper_result_5;
+}
+
diff --git a/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..b6e3d74
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.spvasm
@@ -0,0 +1,151 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 75
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main0 "main0" %value
+               OpEntryPoint Fragment %main1 "main1" %value_1
+               OpEntryPoint Fragment %main2 "main2" %value_2
+               OpEntryPoint Fragment %main3 "main3" %value_3
+               OpEntryPoint Fragment %main4 "main4" %value_4
+               OpEntryPoint Fragment %main5 "main5" %value_5
+               OpExecutionMode %main0 OriginUpperLeft
+               OpExecutionMode %main1 OriginUpperLeft
+               OpExecutionMode %main2 OriginUpperLeft
+               OpExecutionMode %main3 OriginUpperLeft
+               OpExecutionMode %main4 OriginUpperLeft
+               OpExecutionMode %main5 OriginUpperLeft
+               OpName %value "value"
+               OpName %value_1 "value_1"
+               OpName %value_2 "value_2"
+               OpName %value_3 "value_3"
+               OpName %value_4 "value_4"
+               OpName %value_5 "value_5"
+               OpName %main0_inner "main0_inner"
+               OpName %main0 "main0"
+               OpName %main1_inner "main1_inner"
+               OpName %main1 "main1"
+               OpName %main2_inner "main2_inner"
+               OpName %main2 "main2"
+               OpName %main3_inner "main3_inner"
+               OpName %main3 "main3"
+               OpName %main4_inner "main4_inner"
+               OpName %main4 "main4"
+               OpName %main5_inner "main5_inner"
+               OpName %main5 "main5"
+               OpDecorate %value Location 0
+               OpDecorate %value_1 Location 1
+               OpDecorate %value_2 Location 2
+               OpDecorate %value_3 Location 3
+               OpDecorate %value_4 Location 4
+               OpDecorate %value_5 Location 5
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+          %4 = OpConstantNull %int
+      %value = OpVariable %_ptr_Output_int Output %4
+       %uint = OpTypeInt 32 0
+%_ptr_Output_uint = OpTypePointer Output %uint
+          %8 = OpConstantNull %uint
+    %value_1 = OpVariable %_ptr_Output_uint Output %8
+      %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+         %12 = OpConstantNull %float
+    %value_2 = OpVariable %_ptr_Output_float Output %12
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %16 = OpConstantNull %v4float
+    %value_3 = OpVariable %_ptr_Output_v4float Output %16
+       %half = OpTypeFloat 16
+%_ptr_Output_half = OpTypePointer Output %half
+         %20 = OpConstantNull %half
+    %value_4 = OpVariable %_ptr_Output_half Output %20
+     %v3half = OpTypeVector %half 3
+%_ptr_Output_v3half = OpTypePointer Output %v3half
+         %24 = OpConstantNull %v3half
+    %value_5 = OpVariable %_ptr_Output_v3half Output %24
+         %25 = OpTypeFunction %int
+      %int_1 = OpConstant %int 1
+       %void = OpTypeVoid
+         %29 = OpTypeFunction %void
+         %34 = OpTypeFunction %uint
+     %uint_1 = OpConstant %uint 1
+         %41 = OpTypeFunction %float
+    %float_1 = OpConstant %float 1
+         %48 = OpTypeFunction %v4float
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+         %54 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+         %58 = OpTypeFunction %half
+%half_0x1_2p_1 = OpConstant %half 0x1.2p+1
+         %65 = OpTypeFunction %v3half
+%half_0x1_8p_1 = OpConstant %half 0x1.8p+1
+%half_0x1_4p_2 = OpConstant %half 0x1.4p+2
+%half_0x1p_3 = OpConstant %half 0x1p+3
+         %71 = OpConstantComposite %v3half %half_0x1_8p_1 %half_0x1_4p_2 %half_0x1p_3
+%main0_inner = OpFunction %int None %25
+         %27 = OpLabel
+               OpReturnValue %int_1
+               OpFunctionEnd
+      %main0 = OpFunction %void None %29
+         %32 = OpLabel
+         %33 = OpFunctionCall %int %main0_inner
+               OpStore %value %33
+               OpReturn
+               OpFunctionEnd
+%main1_inner = OpFunction %uint None %34
+         %36 = OpLabel
+               OpReturnValue %uint_1
+               OpFunctionEnd
+      %main1 = OpFunction %void None %29
+         %39 = OpLabel
+         %40 = OpFunctionCall %uint %main1_inner
+               OpStore %value_1 %40
+               OpReturn
+               OpFunctionEnd
+%main2_inner = OpFunction %float None %41
+         %43 = OpLabel
+               OpReturnValue %float_1
+               OpFunctionEnd
+      %main2 = OpFunction %void None %29
+         %46 = OpLabel
+         %47 = OpFunctionCall %float %main2_inner
+               OpStore %value_2 %47
+               OpReturn
+               OpFunctionEnd
+%main3_inner = OpFunction %v4float None %48
+         %50 = OpLabel
+               OpReturnValue %54
+               OpFunctionEnd
+      %main3 = OpFunction %void None %29
+         %56 = OpLabel
+         %57 = OpFunctionCall %v4float %main3_inner
+               OpStore %value_3 %57
+               OpReturn
+               OpFunctionEnd
+%main4_inner = OpFunction %half None %58
+         %60 = OpLabel
+               OpReturnValue %half_0x1_2p_1
+               OpFunctionEnd
+      %main4 = OpFunction %void None %29
+         %63 = OpLabel
+         %64 = OpFunctionCall %half %main4_inner
+               OpStore %value_4 %64
+               OpReturn
+               OpFunctionEnd
+%main5_inner = OpFunction %v3half None %65
+         %67 = OpLabel
+               OpReturnValue %71
+               OpFunctionEnd
+      %main5 = OpFunction %void None %29
+         %73 = OpLabel
+         %74 = OpFunctionCall %v3half %main5_inner
+               OpStore %value_5 %74
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..62f7392
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_f16.wgsl.expected.wgsl
@@ -0,0 +1,31 @@
+enable f16;
+
+@fragment
+fn main0() -> @location(0) i32 {
+  return 1;
+}
+
+@fragment
+fn main1() -> @location(1) u32 {
+  return 1u;
+}
+
+@fragment
+fn main2() -> @location(2) f32 {
+  return 1.0;
+}
+
+@fragment
+fn main3() -> @location(3) vec4<f32> {
+  return vec4<f32>(1.0, 2.0, 3.0, 4.0);
+}
+
+@fragment
+fn main4() -> @location(4) f16 {
+  return 2.25h;
+}
+
+@fragment
+fn main5() -> @location(5) vec3<f16> {
+  return vec3<f16>(3.0h, 5.0h, 8.0h);
+}
diff --git a/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl
new file mode 100644
index 0000000..871c4ad
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl
@@ -0,0 +1,15 @@
+enable f16;
+
+struct FragmentOutputs {
+  @location(0) loc0 : i32,
+  @location(1) loc1 : u32,
+  @location(2) loc2 : f32,
+  @location(3) loc3 : vec4<f32>,
+  @location(4) loc4 : f16,
+  @location(5) loc5 : vec3<f16>,
+};
+
+@fragment
+fn main() -> FragmentOutputs {
+  return FragmentOutputs(1, 1u, 1.0, vec4<f32>(1.0, 2.0, 3.0, 4.0), 2.25h, vec3<f16>(3.0h, 5.0h, 8.0h));
+}
diff --git a/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..b3a78ae
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,33 @@
+struct FragmentOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol {
+  int loc0 : SV_Target0;
+  uint loc1 : SV_Target1;
+  float loc2 : SV_Target2;
+  float4 loc3 : SV_Target3;
+  float16_t loc4 : SV_Target4;
+  vector<float16_t, 3> loc5 : SV_Target5;
+};
+
+FragmentOutputs main_inner() {
+  const FragmentOutputs tint_symbol_1 = {1, 1u, 1.0f, float4(1.0f, 2.0f, 3.0f, 4.0f), float16_t(2.25h), vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h))};
+  return tint_symbol_1;
+}
+
+tint_symbol main() {
+  const FragmentOutputs inner_result = main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
diff --git a/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..29537be
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,38 @@
+SKIP: FAILED
+
+struct FragmentOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol {
+  int loc0 : SV_Target0;
+  uint loc1 : SV_Target1;
+  float loc2 : SV_Target2;
+  float4 loc3 : SV_Target3;
+  float16_t loc4 : SV_Target4;
+  vector<float16_t, 3> loc5 : SV_Target5;
+};
+
+FragmentOutputs main_inner() {
+  const FragmentOutputs tint_symbol_1 = {1, 1u, 1.0f, float4(1.0f, 2.0f, 3.0f, 4.0f), float16_t(2.25h), vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h))};
+  return tint_symbol_1;
+}
+
+tint_symbol main() {
+  const FragmentOutputs inner_result = main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x00000289AB322DE0(6,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..db79208
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.glsl
@@ -0,0 +1,34 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 0) out int loc0_1;
+layout(location = 1) out uint loc1_1;
+layout(location = 2) out float loc2_1;
+layout(location = 3) out vec4 loc3_1;
+layout(location = 4) out float16_t loc4_1;
+layout(location = 5) out f16vec3 loc5_1;
+struct FragmentOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  vec4 loc3;
+  float16_t loc4;
+  f16vec3 loc5;
+};
+
+FragmentOutputs tint_symbol() {
+  FragmentOutputs tint_symbol_1 = FragmentOutputs(1, 1u, 1.0f, vec4(1.0f, 2.0f, 3.0f, 4.0f), 2.25hf, f16vec3(3.0hf, 5.0hf, 8.0hf));
+  return tint_symbol_1;
+}
+
+void main() {
+  FragmentOutputs inner_result = tint_symbol();
+  loc0_1 = inner_result.loc0;
+  loc1_1 = inner_result.loc1;
+  loc2_1 = inner_result.loc2;
+  loc3_1 = inner_result.loc3;
+  loc4_1 = inner_result.loc4;
+  loc5_1 = inner_result.loc5;
+  return;
+}
diff --git a/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.msl b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.msl
new file mode 100644
index 0000000..efb99f6
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.msl
@@ -0,0 +1,38 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct FragmentOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  half loc4;
+  half3 loc5;
+};
+
+struct tint_symbol_1 {
+  int loc0 [[color(0)]];
+  uint loc1 [[color(1)]];
+  float loc2 [[color(2)]];
+  float4 loc3 [[color(3)]];
+  half loc4 [[color(4)]];
+  half3 loc5 [[color(5)]];
+};
+
+FragmentOutputs tint_symbol_inner() {
+  FragmentOutputs const tint_symbol_2 = FragmentOutputs{.loc0=1, .loc1=1u, .loc2=1.0f, .loc3=float4(1.0f, 2.0f, 3.0f, 4.0f), .loc4=2.25h, .loc5=half3(3.0h, 5.0h, 8.0h)};
+  return tint_symbol_2;
+}
+
+fragment tint_symbol_1 tint_symbol() {
+  FragmentOutputs const inner_result = tint_symbol_inner();
+  tint_symbol_1 wrapper_result = {};
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
+
diff --git a/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..b68a17b
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.spvasm
@@ -0,0 +1,102 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 53
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %loc0_1 %loc1_1 %loc2_1 %loc3_1 %loc4_1 %loc5_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %loc0_1 "loc0_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %FragmentOutputs "FragmentOutputs"
+               OpMemberName %FragmentOutputs 0 "loc0"
+               OpMemberName %FragmentOutputs 1 "loc1"
+               OpMemberName %FragmentOutputs 2 "loc2"
+               OpMemberName %FragmentOutputs 3 "loc3"
+               OpMemberName %FragmentOutputs 4 "loc4"
+               OpMemberName %FragmentOutputs 5 "loc5"
+               OpName %main_inner "main_inner"
+               OpName %main "main"
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %loc5_1 Location 5
+               OpMemberDecorate %FragmentOutputs 0 Offset 0
+               OpMemberDecorate %FragmentOutputs 1 Offset 4
+               OpMemberDecorate %FragmentOutputs 2 Offset 8
+               OpMemberDecorate %FragmentOutputs 3 Offset 16
+               OpMemberDecorate %FragmentOutputs 4 Offset 32
+               OpMemberDecorate %FragmentOutputs 5 Offset 40
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+          %4 = OpConstantNull %int
+     %loc0_1 = OpVariable %_ptr_Output_int Output %4
+       %uint = OpTypeInt 32 0
+%_ptr_Output_uint = OpTypePointer Output %uint
+          %8 = OpConstantNull %uint
+     %loc1_1 = OpVariable %_ptr_Output_uint Output %8
+      %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+         %12 = OpConstantNull %float
+     %loc2_1 = OpVariable %_ptr_Output_float Output %12
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %16 = OpConstantNull %v4float
+     %loc3_1 = OpVariable %_ptr_Output_v4float Output %16
+       %half = OpTypeFloat 16
+%_ptr_Output_half = OpTypePointer Output %half
+         %20 = OpConstantNull %half
+     %loc4_1 = OpVariable %_ptr_Output_half Output %20
+     %v3half = OpTypeVector %half 3
+%_ptr_Output_v3half = OpTypePointer Output %v3half
+         %24 = OpConstantNull %v3half
+     %loc5_1 = OpVariable %_ptr_Output_v3half Output %24
+%FragmentOutputs = OpTypeStruct %int %uint %float %v4float %half %v3half
+         %25 = OpTypeFunction %FragmentOutputs
+      %int_1 = OpConstant %int 1
+     %uint_1 = OpConstant %uint 1
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+         %35 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+%half_0x1_2p_1 = OpConstant %half 0x1.2p+1
+%half_0x1_8p_1 = OpConstant %half 0x1.8p+1
+%half_0x1_4p_2 = OpConstant %half 0x1.4p+2
+%half_0x1p_3 = OpConstant %half 0x1p+3
+         %40 = OpConstantComposite %v3half %half_0x1_8p_1 %half_0x1_4p_2 %half_0x1p_3
+         %41 = OpConstantComposite %FragmentOutputs %int_1 %uint_1 %float_1 %35 %half_0x1_2p_1 %40
+       %void = OpTypeVoid
+         %42 = OpTypeFunction %void
+ %main_inner = OpFunction %FragmentOutputs None %25
+         %28 = OpLabel
+               OpReturnValue %41
+               OpFunctionEnd
+       %main = OpFunction %void None %42
+         %45 = OpLabel
+         %46 = OpFunctionCall %FragmentOutputs %main_inner
+         %47 = OpCompositeExtract %int %46 0
+               OpStore %loc0_1 %47
+         %48 = OpCompositeExtract %uint %46 1
+               OpStore %loc1_1 %48
+         %49 = OpCompositeExtract %float %46 2
+               OpStore %loc2_1 %49
+         %50 = OpCompositeExtract %v4float %46 3
+               OpStore %loc3_1 %50
+         %51 = OpCompositeExtract %half %46 4
+               OpStore %loc4_1 %51
+         %52 = OpCompositeExtract %v3half %46 5
+               OpStore %loc5_1 %52
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..8621a02
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_locations_struct_f16.wgsl.expected.wgsl
@@ -0,0 +1,21 @@
+enable f16;
+
+struct FragmentOutputs {
+  @location(0)
+  loc0 : i32,
+  @location(1)
+  loc1 : u32,
+  @location(2)
+  loc2 : f32,
+  @location(3)
+  loc3 : vec4<f32>,
+  @location(4)
+  loc4 : f16,
+  @location(5)
+  loc5 : vec3<f16>,
+}
+
+@fragment
+fn main() -> FragmentOutputs {
+  return FragmentOutputs(1, 1u, 1.0, vec4<f32>(1.0, 2.0, 3.0, 4.0), 2.25h, vec3<f16>(3.0h, 5.0h, 8.0h));
+}
diff --git a/test/tint/shader_io/fragment_output_mixed_f16.wgsl b/test/tint/shader_io/fragment_output_mixed_f16.wgsl
new file mode 100644
index 0000000..6de63b3
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_mixed_f16.wgsl
@@ -0,0 +1,17 @@
+enable f16;
+
+struct FragmentOutputs {
+  @location(0) loc0 : i32,
+  @builtin(frag_depth) frag_depth : f32,
+  @location(1) loc1 : u32,
+  @location(2) loc2 : f32,
+  @builtin(sample_mask) sample_mask : u32,
+  @location(3) loc3 : vec4<f32>,
+  @location(4) loc4 : f16,
+  @location(5) loc5 : vec3<f16>,
+};
+
+@fragment
+fn main() -> FragmentOutputs {
+  return FragmentOutputs(1, 2.0, 1u, 1.0, 2u, vec4<f32>(1.0, 2.0, 3.0, 4.0), 2.25h, vec3<f16>(3.0h, 5.0h, 8.0h));
+}
diff --git a/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..ca07eba
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,39 @@
+struct FragmentOutputs {
+  int loc0;
+  float frag_depth;
+  uint loc1;
+  float loc2;
+  uint sample_mask;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol {
+  int loc0 : SV_Target0;
+  uint loc1 : SV_Target1;
+  float loc2 : SV_Target2;
+  float4 loc3 : SV_Target3;
+  float16_t loc4 : SV_Target4;
+  vector<float16_t, 3> loc5 : SV_Target5;
+  float frag_depth : SV_Depth;
+  uint sample_mask : SV_Coverage;
+};
+
+FragmentOutputs main_inner() {
+  const FragmentOutputs tint_symbol_1 = {1, 2.0f, 1u, 1.0f, 2u, float4(1.0f, 2.0f, 3.0f, 4.0f), float16_t(2.25h), vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h))};
+  return tint_symbol_1;
+}
+
+tint_symbol main() {
+  const FragmentOutputs inner_result = main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.frag_depth = inner_result.frag_depth;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.sample_mask = inner_result.sample_mask;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
diff --git a/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..0c99d7f
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,44 @@
+SKIP: FAILED
+
+struct FragmentOutputs {
+  int loc0;
+  float frag_depth;
+  uint loc1;
+  float loc2;
+  uint sample_mask;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol {
+  int loc0 : SV_Target0;
+  uint loc1 : SV_Target1;
+  float loc2 : SV_Target2;
+  float4 loc3 : SV_Target3;
+  float16_t loc4 : SV_Target4;
+  vector<float16_t, 3> loc5 : SV_Target5;
+  float frag_depth : SV_Depth;
+  uint sample_mask : SV_Coverage;
+};
+
+FragmentOutputs main_inner() {
+  const FragmentOutputs tint_symbol_1 = {1, 2.0f, 1u, 1.0f, 2u, float4(1.0f, 2.0f, 3.0f, 4.0f), float16_t(2.25h), vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h))};
+  return tint_symbol_1;
+}
+
+tint_symbol main() {
+  const FragmentOutputs inner_result = main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.frag_depth = inner_result.frag_depth;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.sample_mask = inner_result.sample_mask;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x000002135B880A00(8,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.glsl b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..f635012
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.glsl
@@ -0,0 +1,39 @@
+#version 310 es
+#extension GL_OES_sample_variables : require
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 0) out int loc0_1;
+layout(location = 1) out uint loc1_1;
+layout(location = 2) out float loc2_1;
+layout(location = 3) out vec4 loc3_1;
+layout(location = 4) out float16_t loc4_1;
+layout(location = 5) out f16vec3 loc5_1;
+struct FragmentOutputs {
+  int loc0;
+  float frag_depth;
+  uint loc1;
+  float loc2;
+  uint sample_mask;
+  vec4 loc3;
+  float16_t loc4;
+  f16vec3 loc5;
+};
+
+FragmentOutputs tint_symbol() {
+  FragmentOutputs tint_symbol_1 = FragmentOutputs(1, 2.0f, 1u, 1.0f, 2u, vec4(1.0f, 2.0f, 3.0f, 4.0f), 2.25hf, f16vec3(3.0hf, 5.0hf, 8.0hf));
+  return tint_symbol_1;
+}
+
+void main() {
+  FragmentOutputs inner_result = tint_symbol();
+  loc0_1 = inner_result.loc0;
+  gl_FragDepth = inner_result.frag_depth;
+  loc1_1 = inner_result.loc1;
+  loc2_1 = inner_result.loc2;
+  gl_SampleMask[0] = int(inner_result.sample_mask);
+  loc3_1 = inner_result.loc3;
+  loc4_1 = inner_result.loc4;
+  loc5_1 = inner_result.loc5;
+  return;
+}
diff --git a/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.msl b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.msl
new file mode 100644
index 0000000..59e81a5
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.msl
@@ -0,0 +1,44 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct FragmentOutputs {
+  int loc0;
+  float frag_depth;
+  uint loc1;
+  float loc2;
+  uint sample_mask;
+  float4 loc3;
+  half loc4;
+  half3 loc5;
+};
+
+struct tint_symbol_1 {
+  int loc0 [[color(0)]];
+  uint loc1 [[color(1)]];
+  float loc2 [[color(2)]];
+  float4 loc3 [[color(3)]];
+  half loc4 [[color(4)]];
+  half3 loc5 [[color(5)]];
+  float frag_depth [[depth(any)]];
+  uint sample_mask [[sample_mask]];
+};
+
+FragmentOutputs tint_symbol_inner() {
+  FragmentOutputs const tint_symbol_2 = FragmentOutputs{.loc0=1, .frag_depth=2.0f, .loc1=1u, .loc2=1.0f, .sample_mask=2u, .loc3=float4(1.0f, 2.0f, 3.0f, 4.0f), .loc4=2.25h, .loc5=half3(3.0h, 5.0h, 8.0h)};
+  return tint_symbol_2;
+}
+
+fragment tint_symbol_1 tint_symbol() {
+  FragmentOutputs const inner_result = tint_symbol_inner();
+  tint_symbol_1 wrapper_result = {};
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.frag_depth = inner_result.frag_depth;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.sample_mask = inner_result.sample_mask;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
+
diff --git a/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.spvasm b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..6c6f8d2
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.spvasm
@@ -0,0 +1,123 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 62
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %loc0_1 %frag_depth_1 %loc1_1 %loc2_1 %sample_mask_1 %loc3_1 %loc4_1 %loc5_1
+               OpExecutionMode %main OriginUpperLeft
+               OpExecutionMode %main DepthReplacing
+               OpName %loc0_1 "loc0_1"
+               OpName %frag_depth_1 "frag_depth_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %sample_mask_1 "sample_mask_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %FragmentOutputs "FragmentOutputs"
+               OpMemberName %FragmentOutputs 0 "loc0"
+               OpMemberName %FragmentOutputs 1 "frag_depth"
+               OpMemberName %FragmentOutputs 2 "loc1"
+               OpMemberName %FragmentOutputs 3 "loc2"
+               OpMemberName %FragmentOutputs 4 "sample_mask"
+               OpMemberName %FragmentOutputs 5 "loc3"
+               OpMemberName %FragmentOutputs 6 "loc4"
+               OpMemberName %FragmentOutputs 7 "loc5"
+               OpName %main_inner "main_inner"
+               OpName %main "main"
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %frag_depth_1 BuiltIn FragDepth
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+               OpDecorate %sample_mask_1 BuiltIn SampleMask
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %loc5_1 Location 5
+               OpMemberDecorate %FragmentOutputs 0 Offset 0
+               OpMemberDecorate %FragmentOutputs 1 Offset 4
+               OpMemberDecorate %FragmentOutputs 2 Offset 8
+               OpMemberDecorate %FragmentOutputs 3 Offset 12
+               OpMemberDecorate %FragmentOutputs 4 Offset 16
+               OpMemberDecorate %FragmentOutputs 5 Offset 32
+               OpMemberDecorate %FragmentOutputs 6 Offset 48
+               OpMemberDecorate %FragmentOutputs 7 Offset 56
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+          %4 = OpConstantNull %int
+     %loc0_1 = OpVariable %_ptr_Output_int Output %4
+      %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+          %8 = OpConstantNull %float
+%frag_depth_1 = OpVariable %_ptr_Output_float Output %8
+       %uint = OpTypeInt 32 0
+%_ptr_Output_uint = OpTypePointer Output %uint
+         %12 = OpConstantNull %uint
+     %loc1_1 = OpVariable %_ptr_Output_uint Output %12
+     %loc2_1 = OpVariable %_ptr_Output_float Output %8
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+%_ptr_Output__arr_uint_uint_1 = OpTypePointer Output %_arr_uint_uint_1
+         %18 = OpConstantNull %_arr_uint_uint_1
+%sample_mask_1 = OpVariable %_ptr_Output__arr_uint_uint_1 Output %18
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %22 = OpConstantNull %v4float
+     %loc3_1 = OpVariable %_ptr_Output_v4float Output %22
+       %half = OpTypeFloat 16
+%_ptr_Output_half = OpTypePointer Output %half
+         %26 = OpConstantNull %half
+     %loc4_1 = OpVariable %_ptr_Output_half Output %26
+     %v3half = OpTypeVector %half 3
+%_ptr_Output_v3half = OpTypePointer Output %v3half
+         %30 = OpConstantNull %v3half
+     %loc5_1 = OpVariable %_ptr_Output_v3half Output %30
+%FragmentOutputs = OpTypeStruct %int %float %uint %float %uint %v4float %half %v3half
+         %31 = OpTypeFunction %FragmentOutputs
+      %int_1 = OpConstant %int 1
+    %float_2 = OpConstant %float 2
+    %float_1 = OpConstant %float 1
+     %uint_2 = OpConstant %uint 2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+         %41 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+%half_0x1_2p_1 = OpConstant %half 0x1.2p+1
+%half_0x1_8p_1 = OpConstant %half 0x1.8p+1
+%half_0x1_4p_2 = OpConstant %half 0x1.4p+2
+%half_0x1p_3 = OpConstant %half 0x1p+3
+         %46 = OpConstantComposite %v3half %half_0x1_8p_1 %half_0x1_4p_2 %half_0x1p_3
+         %47 = OpConstantComposite %FragmentOutputs %int_1 %float_2 %uint_1 %float_1 %uint_2 %41 %half_0x1_2p_1 %46
+       %void = OpTypeVoid
+         %48 = OpTypeFunction %void
+ %main_inner = OpFunction %FragmentOutputs None %31
+         %34 = OpLabel
+               OpReturnValue %47
+               OpFunctionEnd
+       %main = OpFunction %void None %48
+         %51 = OpLabel
+         %52 = OpFunctionCall %FragmentOutputs %main_inner
+         %53 = OpCompositeExtract %int %52 0
+               OpStore %loc0_1 %53
+         %54 = OpCompositeExtract %float %52 1
+               OpStore %frag_depth_1 %54
+         %55 = OpCompositeExtract %uint %52 2
+               OpStore %loc1_1 %55
+         %56 = OpCompositeExtract %float %52 3
+               OpStore %loc2_1 %56
+         %57 = OpAccessChain %_ptr_Output_uint %sample_mask_1 %4
+         %58 = OpCompositeExtract %uint %52 4
+               OpStore %57 %58
+         %59 = OpCompositeExtract %v4float %52 5
+               OpStore %loc3_1 %59
+         %60 = OpCompositeExtract %half %52 6
+               OpStore %loc4_1 %60
+         %61 = OpCompositeExtract %v3half %52 7
+               OpStore %loc5_1 %61
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.wgsl b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..4245861
--- /dev/null
+++ b/test/tint/shader_io/fragment_output_mixed_f16.wgsl.expected.wgsl
@@ -0,0 +1,25 @@
+enable f16;
+
+struct FragmentOutputs {
+  @location(0)
+  loc0 : i32,
+  @builtin(frag_depth)
+  frag_depth : f32,
+  @location(1)
+  loc1 : u32,
+  @location(2)
+  loc2 : f32,
+  @builtin(sample_mask)
+  sample_mask : u32,
+  @location(3)
+  loc3 : vec4<f32>,
+  @location(4)
+  loc4 : f16,
+  @location(5)
+  loc5 : vec3<f16>,
+}
+
+@fragment
+fn main() -> FragmentOutputs {
+  return FragmentOutputs(1, 2.0, 1u, 1.0, 2u, vec4<f32>(1.0, 2.0, 3.0, 4.0), 2.25h, vec3<f16>(3.0h, 5.0h, 8.0h));
+}
diff --git a/test/tint/shader_io/shared_struct_different_stages_f16.wgsl b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl
new file mode 100644
index 0000000..eaafdf8
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl
@@ -0,0 +1,18 @@
+enable f16;
+
+struct Interface {
+  @location(1) col1 : f32,
+  @location(2) col2 : f16,
+  @builtin(position) pos : vec4<f32>,
+};
+
+@vertex
+fn vert_main() -> Interface {
+  return Interface(0.4, 0.6h, vec4<f32>());
+}
+
+@fragment
+fn frag_main(colors : Interface) {
+  let r : f32 = colors.col1;
+  let g : f16 = colors.col2;
+}
diff --git a/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..d33f3d2
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,41 @@
+struct Interface {
+  float col1;
+  float16_t col2;
+  float4 pos;
+};
+struct tint_symbol {
+  float col1 : TEXCOORD1;
+  float16_t col2 : TEXCOORD2;
+  float4 pos : SV_Position;
+};
+
+Interface vert_main_inner() {
+  const Interface tint_symbol_3 = {0.400000006f, float16_t(0.599609375h), (0.0f).xxxx};
+  return tint_symbol_3;
+}
+
+tint_symbol vert_main() {
+  const Interface inner_result = vert_main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.col1 = inner_result.col1;
+  wrapper_result.col2 = inner_result.col2;
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  float col1 : TEXCOORD1;
+  float16_t col2 : TEXCOORD2;
+  float4 pos : SV_Position;
+};
+
+void frag_main_inner(Interface colors) {
+  const float r = colors.col1;
+  const float16_t g = colors.col2;
+}
+
+void frag_main(tint_symbol_2 tint_symbol_1) {
+  const Interface tint_symbol_4 = {tint_symbol_1.col1, tint_symbol_1.col2, tint_symbol_1.pos};
+  frag_main_inner(tint_symbol_4);
+  return;
+}
diff --git a/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..b91e9d2
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,46 @@
+SKIP: FAILED
+
+struct Interface {
+  float col1;
+  float16_t col2;
+  float4 pos;
+};
+struct tint_symbol {
+  float col1 : TEXCOORD1;
+  float16_t col2 : TEXCOORD2;
+  float4 pos : SV_Position;
+};
+
+Interface vert_main_inner() {
+  const Interface tint_symbol_3 = {0.400000006f, float16_t(0.599609375h), (0.0f).xxxx};
+  return tint_symbol_3;
+}
+
+tint_symbol vert_main() {
+  const Interface inner_result = vert_main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.col1 = inner_result.col1;
+  wrapper_result.col2 = inner_result.col2;
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  float col1 : TEXCOORD1;
+  float16_t col2 : TEXCOORD2;
+  float4 pos : SV_Position;
+};
+
+void frag_main_inner(Interface colors) {
+  const float r = colors.col1;
+  const float16_t g = colors.col2;
+}
+
+void frag_main(tint_symbol_2 tint_symbol_1) {
+  const Interface tint_symbol_4 = {tint_symbol_1.col1, tint_symbol_1.col2, tint_symbol_1.pos};
+  frag_main_inner(tint_symbol_4);
+  return;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x0000019F5A370630(3,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.glsl b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..2386543
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.glsl
@@ -0,0 +1,48 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+layout(location = 1) out float col1_1;
+layout(location = 2) out float16_t col2_1;
+struct Interface {
+  float col1;
+  float16_t col2;
+  vec4 pos;
+};
+
+Interface vert_main() {
+  Interface tint_symbol = Interface(0.400000006f, 0.599609375hf, vec4(0.0f));
+  return tint_symbol;
+}
+
+void main() {
+  gl_PointSize = 1.0;
+  Interface inner_result = vert_main();
+  col1_1 = inner_result.col1;
+  col2_1 = inner_result.col2;
+  gl_Position = inner_result.pos;
+  gl_Position.y = -(gl_Position.y);
+  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
+  return;
+}
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 1) in float col1_1;
+layout(location = 2) in float16_t col2_1;
+struct Interface {
+  float col1;
+  float16_t col2;
+  vec4 pos;
+};
+
+void frag_main(Interface colors) {
+  float r = colors.col1;
+  float16_t g = colors.col2;
+}
+
+void main() {
+  Interface tint_symbol = Interface(col1_1, col2_1, gl_FragCoord);
+  frag_main(tint_symbol);
+  return;
+}
diff --git a/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.msl b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.msl
new file mode 100644
index 0000000..5bccb63
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.msl
@@ -0,0 +1,45 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct Interface {
+  float col1;
+  half col2;
+  float4 pos;
+};
+
+struct tint_symbol {
+  float col1 [[user(locn1)]];
+  half col2 [[user(locn2)]];
+  float4 pos [[position]];
+};
+
+Interface vert_main_inner() {
+  Interface const tint_symbol_3 = Interface{.col1=0.400000006f, .col2=0.599609375h, .pos=float4(0.0f)};
+  return tint_symbol_3;
+}
+
+vertex tint_symbol vert_main() {
+  Interface const inner_result = vert_main_inner();
+  tint_symbol wrapper_result = {};
+  wrapper_result.col1 = inner_result.col1;
+  wrapper_result.col2 = inner_result.col2;
+  wrapper_result.pos = inner_result.pos;
+  return wrapper_result;
+}
+
+struct tint_symbol_2 {
+  float col1 [[user(locn1)]];
+  half col2 [[user(locn2)]];
+};
+
+void frag_main_inner(Interface colors) {
+  float const r = colors.col1;
+  half const g = colors.col2;
+}
+
+fragment void frag_main(float4 pos [[position]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  Interface const tint_symbol_4 = {.col1=tint_symbol_1.col1, .col2=tint_symbol_1.col2, .pos=pos};
+  frag_main_inner(tint_symbol_4);
+  return;
+}
+
diff --git a/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.spvasm b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..9260b6f
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.spvasm
@@ -0,0 +1,100 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 49
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %vert_main "vert_main" %col1_1 %col2_1 %pos_1 %vertex_point_size
+               OpEntryPoint Fragment %frag_main "frag_main" %col1_2 %col2_2 %pos_2
+               OpExecutionMode %frag_main OriginUpperLeft
+               OpName %col1_1 "col1_1"
+               OpName %col2_1 "col2_1"
+               OpName %pos_1 "pos_1"
+               OpName %vertex_point_size "vertex_point_size"
+               OpName %col1_2 "col1_2"
+               OpName %col2_2 "col2_2"
+               OpName %pos_2 "pos_2"
+               OpName %Interface "Interface"
+               OpMemberName %Interface 0 "col1"
+               OpMemberName %Interface 1 "col2"
+               OpMemberName %Interface 2 "pos"
+               OpName %vert_main_inner "vert_main_inner"
+               OpName %vert_main "vert_main"
+               OpName %frag_main_inner "frag_main_inner"
+               OpName %colors "colors"
+               OpName %frag_main "frag_main"
+               OpDecorate %col1_1 Location 1
+               OpDecorate %col2_1 Location 2
+               OpDecorate %pos_1 BuiltIn Position
+               OpDecorate %vertex_point_size BuiltIn PointSize
+               OpDecorate %col1_2 Location 1
+               OpDecorate %col2_2 Location 2
+               OpDecorate %pos_2 BuiltIn FragCoord
+               OpMemberDecorate %Interface 0 Offset 0
+               OpMemberDecorate %Interface 1 Offset 4
+               OpMemberDecorate %Interface 2 Offset 16
+      %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+          %4 = OpConstantNull %float
+     %col1_1 = OpVariable %_ptr_Output_float Output %4
+       %half = OpTypeFloat 16
+%_ptr_Output_half = OpTypePointer Output %half
+          %8 = OpConstantNull %half
+     %col2_1 = OpVariable %_ptr_Output_half Output %8
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %12 = OpConstantNull %v4float
+      %pos_1 = OpVariable %_ptr_Output_v4float Output %12
+%vertex_point_size = OpVariable %_ptr_Output_float Output %4
+%_ptr_Input_float = OpTypePointer Input %float
+     %col1_2 = OpVariable %_ptr_Input_float Input
+%_ptr_Input_half = OpTypePointer Input %half
+     %col2_2 = OpVariable %_ptr_Input_half Input
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+      %pos_2 = OpVariable %_ptr_Input_v4float Input
+  %Interface = OpTypeStruct %float %half %v4float
+         %20 = OpTypeFunction %Interface
+%float_0_400000006 = OpConstant %float 0.400000006
+%half_0x1_33pn1 = OpConstant %half 0x1.33p-1
+         %26 = OpConstantComposite %Interface %float_0_400000006 %half_0x1_33pn1 %12
+       %void = OpTypeVoid
+         %27 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+         %36 = OpTypeFunction %void %Interface
+%vert_main_inner = OpFunction %Interface None %20
+         %23 = OpLabel
+               OpReturnValue %26
+               OpFunctionEnd
+  %vert_main = OpFunction %void None %27
+         %30 = OpLabel
+         %31 = OpFunctionCall %Interface %vert_main_inner
+         %32 = OpCompositeExtract %float %31 0
+               OpStore %col1_1 %32
+         %33 = OpCompositeExtract %half %31 1
+               OpStore %col2_1 %33
+         %34 = OpCompositeExtract %v4float %31 2
+               OpStore %pos_1 %34
+               OpStore %vertex_point_size %float_1
+               OpReturn
+               OpFunctionEnd
+%frag_main_inner = OpFunction %void None %36
+     %colors = OpFunctionParameter %Interface
+         %39 = OpLabel
+         %40 = OpCompositeExtract %float %colors 0
+         %41 = OpCompositeExtract %half %colors 1
+               OpReturn
+               OpFunctionEnd
+  %frag_main = OpFunction %void None %27
+         %43 = OpLabel
+         %45 = OpLoad %float %col1_2
+         %46 = OpLoad %half %col2_2
+         %47 = OpLoad %v4float %pos_2
+         %48 = OpCompositeConstruct %Interface %45 %46 %47
+         %44 = OpFunctionCall %void %frag_main_inner %48
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.wgsl b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..44c3dc6
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_different_stages_f16.wgsl.expected.wgsl
@@ -0,0 +1,21 @@
+enable f16;
+
+struct Interface {
+  @location(1)
+  col1 : f32,
+  @location(2)
+  col2 : f16,
+  @builtin(position)
+  pos : vec4<f32>,
+}
+
+@vertex
+fn vert_main() -> Interface {
+  return Interface(0.4, 0.599609375h, vec4<f32>());
+}
+
+@fragment
+fn frag_main(colors : Interface) {
+  let r : f32 = colors.col1;
+  let g : f16 = colors.col2;
+}
diff --git a/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl
new file mode 100644
index 0000000..43b3231
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl
@@ -0,0 +1,22 @@
+enable f16;
+
+struct S {
+  @align(64) @location(0) f : f32,
+  @size(32) @location(1) @interpolate(flat) u : u32,
+  @align(128) @builtin(position) v : vec4<f32>,
+  @align(32) @location(2) x : f16,
+  @align(64) @location(3) y : vec3<f16>,
+};
+
+@group(0) @binding(0)
+var<storage, read_write> output : S;
+
+@fragment
+fn frag_main(input : S) {
+  let f : f32 = input.f;
+  let u : u32 = input.u;
+  let v : vec4<f32> = input.v;
+  let x : f16 = input.x;
+  let y : vec3<f16> = input.y;
+  output = input;
+}
diff --git a/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..614cc72
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,40 @@
+struct S {
+  float f;
+  uint u;
+  float4 v;
+  float16_t x;
+  vector<float16_t, 3> y;
+};
+
+RWByteAddressBuffer output : register(u0, space0);
+
+struct tint_symbol_1 {
+  float f : TEXCOORD0;
+  nointerpolation uint u : TEXCOORD1;
+  float16_t x : TEXCOORD2;
+  vector<float16_t, 3> y : TEXCOORD3;
+  float4 v : SV_Position;
+};
+
+void tint_symbol_2(RWByteAddressBuffer buffer, uint offset, S value) {
+  buffer.Store((offset + 0u), asuint(value.f));
+  buffer.Store((offset + 4u), asuint(value.u));
+  buffer.Store4((offset + 128u), asuint(value.v));
+  buffer.Store<float16_t>((offset + 160u), value.x);
+  buffer.Store<vector<float16_t, 3> >((offset + 192u), value.y);
+}
+
+void frag_main_inner(S input) {
+  const float f = input.f;
+  const uint u = input.u;
+  const float4 v = input.v;
+  const float16_t x = input.x;
+  const vector<float16_t, 3> y = input.y;
+  tint_symbol_2(output, 0u, input);
+}
+
+void frag_main(tint_symbol_1 tint_symbol) {
+  const S tint_symbol_8 = {tint_symbol.f, tint_symbol.u, tint_symbol.v, tint_symbol.x, tint_symbol.y};
+  frag_main_inner(tint_symbol_8);
+  return;
+}
diff --git a/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..10c2dfe
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,45 @@
+SKIP: FAILED
+
+struct S {
+  float f;
+  uint u;
+  float4 v;
+  float16_t x;
+  vector<float16_t, 3> y;
+};
+
+RWByteAddressBuffer output : register(u0, space0);
+
+struct tint_symbol_1 {
+  float f : TEXCOORD0;
+  nointerpolation uint u : TEXCOORD1;
+  float16_t x : TEXCOORD2;
+  vector<float16_t, 3> y : TEXCOORD3;
+  float4 v : SV_Position;
+};
+
+void tint_symbol_2(RWByteAddressBuffer buffer, uint offset, S value) {
+  buffer.Store((offset + 0u), asuint(value.f));
+  buffer.Store((offset + 4u), asuint(value.u));
+  buffer.Store4((offset + 128u), asuint(value.v));
+  buffer.Store<float16_t>((offset + 160u), value.x);
+  buffer.Store<vector<float16_t, 3> >((offset + 192u), value.y);
+}
+
+void frag_main_inner(S input) {
+  const float f = input.f;
+  const uint u = input.u;
+  const float4 v = input.v;
+  const float16_t x = input.x;
+  const vector<float16_t, 3> y = input.y;
+  tint_symbol_2(output, 0u, input);
+}
+
+void frag_main(tint_symbol_1 tint_symbol) {
+  const S tint_symbol_8 = {tint_symbol.f, tint_symbol.u, tint_symbol.v, tint_symbol.x, tint_symbol.y};
+  frag_main_inner(tint_symbol_8);
+  return;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x00000223CE4E5B80(5,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.glsl b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..ad5e592
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.glsl
@@ -0,0 +1,89 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+precision mediump float;
+
+layout(location = 0) in float f_1;
+layout(location = 1) flat in uint u_1;
+layout(location = 2) in float16_t x_1;
+layout(location = 3) in f16vec3 y_1;
+struct S {
+  float f;
+  uint u;
+  uint pad;
+  uint pad_1;
+  uint pad_2;
+  uint pad_3;
+  uint pad_4;
+  uint pad_5;
+  uint pad_6;
+  uint pad_7;
+  uint pad_8;
+  uint pad_9;
+  uint pad_10;
+  uint pad_11;
+  uint pad_12;
+  uint pad_13;
+  uint pad_14;
+  uint pad_15;
+  uint pad_16;
+  uint pad_17;
+  uint pad_18;
+  uint pad_19;
+  uint pad_20;
+  uint pad_21;
+  uint pad_22;
+  uint pad_23;
+  uint pad_24;
+  uint pad_25;
+  uint pad_26;
+  uint pad_27;
+  uint pad_28;
+  uint pad_29;
+  vec4 v;
+  uint pad_30;
+  uint pad_31;
+  uint pad_32;
+  uint pad_33;
+  float16_t x;
+  uint pad_34;
+  uint pad_35;
+  uint pad_36;
+  uint pad_37;
+  uint pad_38;
+  uint pad_39;
+  uint pad_40;
+  f16vec3 y;
+  uint pad_41;
+  uint pad_42;
+  uint pad_43;
+  uint pad_44;
+  uint pad_45;
+  uint pad_46;
+  uint pad_47;
+  uint pad_48;
+  uint pad_49;
+  uint pad_50;
+  uint pad_51;
+  uint pad_52;
+  uint pad_53;
+  uint pad_54;
+};
+
+layout(binding = 0, std430) buffer tint_symbol_block_ssbo {
+  S inner;
+} tint_symbol;
+
+void frag_main(S tint_symbol_1) {
+  float f = tint_symbol_1.f;
+  uint u = tint_symbol_1.u;
+  vec4 v = tint_symbol_1.v;
+  float16_t x = tint_symbol_1.x;
+  f16vec3 y = tint_symbol_1.y;
+  tint_symbol.inner = tint_symbol_1;
+}
+
+void main() {
+  S tint_symbol_2 = S(f_1, u_1, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, gl_FragCoord, 0u, 0u, 0u, 0u, x_1, 0u, 0u, 0u, 0u, 0u, 0u, 0u, y_1, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u);
+  frag_main(tint_symbol_2);
+  return;
+}
diff --git a/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.msl b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.msl
new file mode 100644
index 0000000..ad33a2a
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.msl
@@ -0,0 +1,50 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct S {
+  /* 0x0000 */ float f;
+  /* 0x0004 */ uint u;
+  /* 0x0008 */ tint_array<int8_t, 120> tint_pad;
+  /* 0x0080 */ float4 v;
+  /* 0x0090 */ tint_array<int8_t, 16> tint_pad_1;
+  /* 0x00a0 */ half x;
+  /* 0x00a2 */ tint_array<int8_t, 30> tint_pad_2;
+  /* 0x00c0 */ packed_half3 y;
+  /* 0x00c6 */ tint_array<int8_t, 58> tint_pad_3;
+};
+
+struct tint_symbol_1 {
+  float f [[user(locn0)]];
+  uint u [[user(locn1)]] [[flat]];
+  half x [[user(locn2)]];
+  half3 y [[user(locn3)]];
+};
+
+void frag_main_inner(S input, device S* const tint_symbol_3) {
+  float const f = input.f;
+  uint const u = input.u;
+  float4 const v = input.v;
+  half const x = input.x;
+  half3 const y = half3(input.y);
+  *(tint_symbol_3) = input;
+}
+
+fragment void frag_main(device S* tint_symbol_4 [[buffer(0)]], float4 v [[position]], tint_symbol_1 tint_symbol [[stage_in]]) {
+  S const tint_symbol_2 = {.f=tint_symbol.f, .u=tint_symbol.u, .v=v, .x=tint_symbol.x, .y=tint_symbol.y};
+  frag_main_inner(tint_symbol_2, tint_symbol_4);
+  return;
+}
+
diff --git a/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.spvasm b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..52ab2f6
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.spvasm
@@ -0,0 +1,92 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 43
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %frag_main "frag_main" %f_1 %u_1 %v_1 %x_1 %y_1
+               OpExecutionMode %frag_main OriginUpperLeft
+               OpName %f_1 "f_1"
+               OpName %u_1 "u_1"
+               OpName %v_1 "v_1"
+               OpName %x_1 "x_1"
+               OpName %y_1 "y_1"
+               OpName %output_block "output_block"
+               OpMemberName %output_block 0 "inner"
+               OpName %S "S"
+               OpMemberName %S 0 "f"
+               OpMemberName %S 1 "u"
+               OpMemberName %S 2 "v"
+               OpMemberName %S 3 "x"
+               OpMemberName %S 4 "y"
+               OpName %output "output"
+               OpName %frag_main_inner "frag_main_inner"
+               OpName %input "input"
+               OpName %frag_main "frag_main"
+               OpDecorate %f_1 Location 0
+               OpDecorate %u_1 Location 1
+               OpDecorate %u_1 Flat
+               OpDecorate %v_1 BuiltIn FragCoord
+               OpDecorate %x_1 Location 2
+               OpDecorate %y_1 Location 3
+               OpDecorate %output_block Block
+               OpMemberDecorate %output_block 0 Offset 0
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpMemberDecorate %S 2 Offset 128
+               OpMemberDecorate %S 3 Offset 160
+               OpMemberDecorate %S 4 Offset 192
+               OpDecorate %output DescriptorSet 0
+               OpDecorate %output Binding 0
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+        %f_1 = OpVariable %_ptr_Input_float Input
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+        %u_1 = OpVariable %_ptr_Input_uint Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+        %v_1 = OpVariable %_ptr_Input_v4float Input
+       %half = OpTypeFloat 16
+%_ptr_Input_half = OpTypePointer Input %half
+        %x_1 = OpVariable %_ptr_Input_half Input
+     %v3half = OpTypeVector %half 3
+%_ptr_Input_v3half = OpTypePointer Input %v3half
+        %y_1 = OpVariable %_ptr_Input_v3half Input
+          %S = OpTypeStruct %float %uint %v4float %half %v3half
+%output_block = OpTypeStruct %S
+%_ptr_StorageBuffer_output_block = OpTypePointer StorageBuffer %output_block
+     %output = OpVariable %_ptr_StorageBuffer_output_block StorageBuffer
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void %S
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_S = OpTypePointer StorageBuffer %S
+         %33 = OpTypeFunction %void
+%frag_main_inner = OpFunction %void None %20
+      %input = OpFunctionParameter %S
+         %24 = OpLabel
+         %25 = OpCompositeExtract %float %input 0
+         %26 = OpCompositeExtract %uint %input 1
+         %27 = OpCompositeExtract %v4float %input 2
+         %28 = OpCompositeExtract %half %input 3
+         %29 = OpCompositeExtract %v3half %input 4
+         %32 = OpAccessChain %_ptr_StorageBuffer_S %output %uint_0
+               OpStore %32 %input
+               OpReturn
+               OpFunctionEnd
+  %frag_main = OpFunction %void None %33
+         %35 = OpLabel
+         %37 = OpLoad %float %f_1
+         %38 = OpLoad %uint %u_1
+         %39 = OpLoad %v4float %v_1
+         %40 = OpLoad %half %x_1
+         %41 = OpLoad %v3half %y_1
+         %42 = OpCompositeConstruct %S %37 %38 %39 %40 %41
+         %36 = OpFunctionCall %void %frag_main_inner %42
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.wgsl b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..f1bbc34
--- /dev/null
+++ b/test/tint/shader_io/shared_struct_storage_buffer_f16.wgsl.expected.wgsl
@@ -0,0 +1,26 @@
+enable f16;
+
+struct S {
+  @align(64) @location(0)
+  f : f32,
+  @size(32) @location(1) @interpolate(flat)
+  u : u32,
+  @align(128) @builtin(position)
+  v : vec4<f32>,
+  @align(32) @location(2)
+  x : f16,
+  @align(64) @location(3)
+  y : vec3<f16>,
+}
+
+@group(0) @binding(0) var<storage, read_write> output : S;
+
+@fragment
+fn frag_main(input : S) {
+  let f : f32 = input.f;
+  let u : u32 = input.u;
+  let v : vec4<f32> = input.v;
+  let x : f16 = input.x;
+  let y : vec3<f16> = input.y;
+  output = input;
+}
diff --git a/test/tint/shader_io/vertex_input_locations_f16.wgsl b/test/tint/shader_io/vertex_input_locations_f16.wgsl
new file mode 100644
index 0000000..231bc4e5
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_f16.wgsl
@@ -0,0 +1,19 @@
+enable f16;
+
+@vertex
+fn main(
+  @location(0) loc0 : i32,
+  @location(1) loc1 : u32,
+  @location(2) loc2 : f32,
+  @location(3) loc3 : vec4<f32>,
+  @location(4) loc4 : f16,
+  @location(5) loc5 : vec3<f16>,
+) -> @builtin(position) vec4<f32> {
+  let i : i32 = loc0;
+  let u : u32 = loc1;
+  let f : f32 = loc2;
+  let v : vec4<f32> = loc3;
+  let x : f16 = loc4;
+  let y : vec3<f16> = loc5;
+  return vec4<f32>();
+}
diff --git a/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..6962a01
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,28 @@
+struct tint_symbol_1 {
+  int loc0 : TEXCOORD0;
+  uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Position;
+};
+
+float4 main_inner(int loc0, uint loc1, float loc2, float4 loc3, float16_t loc4, vector<float16_t, 3> loc5) {
+  const int i = loc0;
+  const uint u = loc1;
+  const float f = loc2;
+  const float4 v = loc3;
+  const float16_t x = loc4;
+  const vector<float16_t, 3> y = loc5;
+  return (0.0f).xxxx;
+}
+
+tint_symbol_2 main(tint_symbol_1 tint_symbol) {
+  const float4 inner_result = main_inner(tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
diff --git a/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..622d2fb
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,33 @@
+SKIP: FAILED
+
+struct tint_symbol_1 {
+  int loc0 : TEXCOORD0;
+  uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Position;
+};
+
+float4 main_inner(int loc0, uint loc1, float loc2, float4 loc3, float16_t loc4, vector<float16_t, 3> loc5) {
+  const int i = loc0;
+  const uint u = loc1;
+  const float f = loc2;
+  const float4 v = loc3;
+  const float16_t x = loc4;
+  const vector<float16_t, 3> y = loc5;
+  return (0.0f).xxxx;
+}
+
+tint_symbol_2 main(tint_symbol_1 tint_symbol) {
+  const float4 inner_result = main_inner(tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x0000016E3C1012A0(6,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..7b5aff3
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.glsl
@@ -0,0 +1,27 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+layout(location = 0) in int loc0_1;
+layout(location = 1) in uint loc1_1;
+layout(location = 2) in float loc2_1;
+layout(location = 3) in vec4 loc3_1;
+layout(location = 4) in float16_t loc4_1;
+layout(location = 5) in f16vec3 loc5_1;
+vec4 tint_symbol(int loc0, uint loc1, float loc2, vec4 loc3, float16_t loc4, f16vec3 loc5) {
+  int i = loc0;
+  uint u = loc1;
+  float f = loc2;
+  vec4 v = loc3;
+  float16_t x = loc4;
+  f16vec3 y = loc5;
+  return vec4(0.0f);
+}
+
+void main() {
+  gl_PointSize = 1.0;
+  vec4 inner_result = tint_symbol(loc0_1, loc1_1, loc2_1, loc3_1, loc4_1, loc5_1);
+  gl_Position = inner_result;
+  gl_Position.y = -(gl_Position.y);
+  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
+  return;
+}
diff --git a/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.msl b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.msl
new file mode 100644
index 0000000..7c07f6a
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.msl
@@ -0,0 +1,33 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_2 {
+  int loc0 [[attribute(0)]];
+  uint loc1 [[attribute(1)]];
+  float loc2 [[attribute(2)]];
+  float4 loc3 [[attribute(3)]];
+  half loc4 [[attribute(4)]];
+  half3 loc5 [[attribute(5)]];
+};
+
+struct tint_symbol_3 {
+  float4 value [[position]];
+};
+
+float4 tint_symbol_inner(int loc0, uint loc1, float loc2, float4 loc3, half loc4, half3 loc5) {
+  int const i = loc0;
+  uint const u = loc1;
+  float const f = loc2;
+  float4 const v = loc3;
+  half const x = loc4;
+  half3 const y = loc5;
+  return float4(0.0f);
+}
+
+vertex tint_symbol_3 tint_symbol(tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  float4 const inner_result = tint_symbol_inner(tint_symbol_1.loc0, tint_symbol_1.loc1, tint_symbol_1.loc2, tint_symbol_1.loc3, tint_symbol_1.loc4, tint_symbol_1.loc5);
+  tint_symbol_3 wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
diff --git a/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..bf24647
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.spvasm
@@ -0,0 +1,87 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 46
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %loc0_1 %loc1_1 %loc2_1 %loc3_1 %loc4_1 %loc5_1 %value %vertex_point_size
+               OpName %loc0_1 "loc0_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %value "value"
+               OpName %vertex_point_size "vertex_point_size"
+               OpName %main_inner "main_inner"
+               OpName %loc0 "loc0"
+               OpName %loc1 "loc1"
+               OpName %loc2 "loc2"
+               OpName %loc3 "loc3"
+               OpName %loc4 "loc4"
+               OpName %loc5 "loc5"
+               OpName %main "main"
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %loc5_1 Location 5
+               OpDecorate %value BuiltIn Position
+               OpDecorate %vertex_point_size BuiltIn PointSize
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+     %loc0_1 = OpVariable %_ptr_Input_int Input
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+     %loc1_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+     %loc2_1 = OpVariable %_ptr_Input_float Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+     %loc3_1 = OpVariable %_ptr_Input_v4float Input
+       %half = OpTypeFloat 16
+%_ptr_Input_half = OpTypePointer Input %half
+     %loc4_1 = OpVariable %_ptr_Input_half Input
+     %v3half = OpTypeVector %half 3
+%_ptr_Input_v3half = OpTypePointer Input %v3half
+     %loc5_1 = OpVariable %_ptr_Input_v3half Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %21 = OpConstantNull %v4float
+      %value = OpVariable %_ptr_Output_v4float Output %21
+%_ptr_Output_float = OpTypePointer Output %float
+         %24 = OpConstantNull %float
+%vertex_point_size = OpVariable %_ptr_Output_float Output %24
+         %25 = OpTypeFunction %v4float %int %uint %float %v4float %half %v3half
+       %void = OpTypeVoid
+         %34 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+ %main_inner = OpFunction %v4float None %25
+       %loc0 = OpFunctionParameter %int
+       %loc1 = OpFunctionParameter %uint
+       %loc2 = OpFunctionParameter %float
+       %loc3 = OpFunctionParameter %v4float
+       %loc4 = OpFunctionParameter %half
+       %loc5 = OpFunctionParameter %v3half
+         %33 = OpLabel
+               OpReturnValue %21
+               OpFunctionEnd
+       %main = OpFunction %void None %34
+         %37 = OpLabel
+         %39 = OpLoad %int %loc0_1
+         %40 = OpLoad %uint %loc1_1
+         %41 = OpLoad %float %loc2_1
+         %42 = OpLoad %v4float %loc3_1
+         %43 = OpLoad %half %loc4_1
+         %44 = OpLoad %v3half %loc5_1
+         %38 = OpFunctionCall %v4float %main_inner %39 %40 %41 %42 %43 %44
+               OpStore %value %38
+               OpStore %vertex_point_size %float_1
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..8a4c606
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_f16.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable f16;
+
+@vertex
+fn main(@location(0) loc0 : i32, @location(1) loc1 : u32, @location(2) loc2 : f32, @location(3) loc3 : vec4<f32>, @location(4) loc4 : f16, @location(5) loc5 : vec3<f16>) -> @builtin(position) vec4<f32> {
+  let i : i32 = loc0;
+  let u : u32 = loc1;
+  let f : f32 = loc2;
+  let v : vec4<f32> = loc3;
+  let x : f16 = loc4;
+  let y : vec3<f16> = loc5;
+  return vec4<f32>();
+}
diff --git a/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl
new file mode 100644
index 0000000..5c85329
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl
@@ -0,0 +1,21 @@
+enable f16;
+
+struct VertexInputs {
+  @location(0) loc0 : i32,
+  @location(1) loc1 : u32,
+  @location(2) loc2 : f32,
+  @location(3) loc3 : vec4<f32>,
+  @location(4) loc4 : f16,
+  @location(5) loc5 : vec3<f16>,
+};
+
+@vertex
+fn main(inputs : VertexInputs) -> @builtin(position) vec4<f32> {
+  let i : i32 = inputs.loc0;
+  let u : u32 = inputs.loc1;
+  let f : f32 = inputs.loc2;
+  let v : vec4<f32> = inputs.loc3;
+  let x : f16 = inputs.loc4;
+  let y : vec3<f16> = inputs.loc5;
+  return vec4<f32>();
+}
diff --git a/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..dcb2c0d
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,37 @@
+struct VertexInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol_1 {
+  int loc0 : TEXCOORD0;
+  uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Position;
+};
+
+float4 main_inner(VertexInputs inputs) {
+  const int i = inputs.loc0;
+  const uint u = inputs.loc1;
+  const float f = inputs.loc2;
+  const float4 v = inputs.loc3;
+  const float16_t x = inputs.loc4;
+  const vector<float16_t, 3> y = inputs.loc5;
+  return (0.0f).xxxx;
+}
+
+tint_symbol_2 main(tint_symbol_1 tint_symbol) {
+  const VertexInputs tint_symbol_3 = {tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5};
+  const float4 inner_result = main_inner(tint_symbol_3);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
diff --git a/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..fcb2564
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,42 @@
+SKIP: FAILED
+
+struct VertexInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol_1 {
+  int loc0 : TEXCOORD0;
+  uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Position;
+};
+
+float4 main_inner(VertexInputs inputs) {
+  const int i = inputs.loc0;
+  const uint u = inputs.loc1;
+  const float f = inputs.loc2;
+  const float4 v = inputs.loc3;
+  const float16_t x = inputs.loc4;
+  const vector<float16_t, 3> y = inputs.loc5;
+  return (0.0f).xxxx;
+}
+
+tint_symbol_2 main(tint_symbol_1 tint_symbol) {
+  const VertexInputs tint_symbol_3 = {tint_symbol.loc0, tint_symbol.loc1, tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc4, tint_symbol.loc5};
+  const float4 inner_result = main_inner(tint_symbol_3);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x0000014EC0050950(6,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..a1f168b
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.glsl
@@ -0,0 +1,37 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+layout(location = 0) in int loc0_1;
+layout(location = 1) in uint loc1_1;
+layout(location = 2) in float loc2_1;
+layout(location = 3) in vec4 loc3_1;
+layout(location = 4) in float16_t loc4_1;
+layout(location = 5) in f16vec3 loc5_1;
+struct VertexInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  vec4 loc3;
+  float16_t loc4;
+  f16vec3 loc5;
+};
+
+vec4 tint_symbol(VertexInputs inputs) {
+  int i = inputs.loc0;
+  uint u = inputs.loc1;
+  float f = inputs.loc2;
+  vec4 v = inputs.loc3;
+  float16_t x = inputs.loc4;
+  f16vec3 y = inputs.loc5;
+  return vec4(0.0f);
+}
+
+void main() {
+  gl_PointSize = 1.0;
+  VertexInputs tint_symbol_1 = VertexInputs(loc0_1, loc1_1, loc2_1, loc3_1, loc4_1, loc5_1);
+  vec4 inner_result = tint_symbol(tint_symbol_1);
+  gl_Position = inner_result;
+  gl_Position.y = -(gl_Position.y);
+  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
+  return;
+}
diff --git a/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.msl b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.msl
new file mode 100644
index 0000000..31a38fa
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.msl
@@ -0,0 +1,43 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct VertexInputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  half loc4;
+  half3 loc5;
+};
+
+struct tint_symbol_2 {
+  int loc0 [[attribute(0)]];
+  uint loc1 [[attribute(1)]];
+  float loc2 [[attribute(2)]];
+  float4 loc3 [[attribute(3)]];
+  half loc4 [[attribute(4)]];
+  half3 loc5 [[attribute(5)]];
+};
+
+struct tint_symbol_3 {
+  float4 value [[position]];
+};
+
+float4 tint_symbol_inner(VertexInputs inputs) {
+  int const i = inputs.loc0;
+  uint const u = inputs.loc1;
+  float const f = inputs.loc2;
+  float4 const v = inputs.loc3;
+  half const x = inputs.loc4;
+  half3 const y = inputs.loc5;
+  return float4(0.0f);
+}
+
+vertex tint_symbol_3 tint_symbol(tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  VertexInputs const tint_symbol_4 = {.loc0=tint_symbol_1.loc0, .loc1=tint_symbol_1.loc1, .loc2=tint_symbol_1.loc2, .loc3=tint_symbol_1.loc3, .loc4=tint_symbol_1.loc4, .loc5=tint_symbol_1.loc5};
+  float4 const inner_result = tint_symbol_inner(tint_symbol_4);
+  tint_symbol_3 wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
diff --git a/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..a9c083c
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.spvasm
@@ -0,0 +1,98 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 49
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %loc0_1 %loc1_1 %loc2_1 %loc3_1 %loc4_1 %loc5_1 %value %vertex_point_size
+               OpName %loc0_1 "loc0_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %value "value"
+               OpName %vertex_point_size "vertex_point_size"
+               OpName %VertexInputs "VertexInputs"
+               OpMemberName %VertexInputs 0 "loc0"
+               OpMemberName %VertexInputs 1 "loc1"
+               OpMemberName %VertexInputs 2 "loc2"
+               OpMemberName %VertexInputs 3 "loc3"
+               OpMemberName %VertexInputs 4 "loc4"
+               OpMemberName %VertexInputs 5 "loc5"
+               OpName %main_inner "main_inner"
+               OpName %inputs "inputs"
+               OpName %main "main"
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %loc5_1 Location 5
+               OpDecorate %value BuiltIn Position
+               OpDecorate %vertex_point_size BuiltIn PointSize
+               OpMemberDecorate %VertexInputs 0 Offset 0
+               OpMemberDecorate %VertexInputs 1 Offset 4
+               OpMemberDecorate %VertexInputs 2 Offset 8
+               OpMemberDecorate %VertexInputs 3 Offset 16
+               OpMemberDecorate %VertexInputs 4 Offset 32
+               OpMemberDecorate %VertexInputs 5 Offset 40
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+     %loc0_1 = OpVariable %_ptr_Input_int Input
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+     %loc1_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+     %loc2_1 = OpVariable %_ptr_Input_float Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+     %loc3_1 = OpVariable %_ptr_Input_v4float Input
+       %half = OpTypeFloat 16
+%_ptr_Input_half = OpTypePointer Input %half
+     %loc4_1 = OpVariable %_ptr_Input_half Input
+     %v3half = OpTypeVector %half 3
+%_ptr_Input_v3half = OpTypePointer Input %v3half
+     %loc5_1 = OpVariable %_ptr_Input_v3half Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %21 = OpConstantNull %v4float
+      %value = OpVariable %_ptr_Output_v4float Output %21
+%_ptr_Output_float = OpTypePointer Output %float
+         %24 = OpConstantNull %float
+%vertex_point_size = OpVariable %_ptr_Output_float Output %24
+%VertexInputs = OpTypeStruct %int %uint %float %v4float %half %v3half
+         %25 = OpTypeFunction %v4float %VertexInputs
+       %void = OpTypeVoid
+         %36 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+ %main_inner = OpFunction %v4float None %25
+     %inputs = OpFunctionParameter %VertexInputs
+         %29 = OpLabel
+         %30 = OpCompositeExtract %int %inputs 0
+         %31 = OpCompositeExtract %uint %inputs 1
+         %32 = OpCompositeExtract %float %inputs 2
+         %33 = OpCompositeExtract %v4float %inputs 3
+         %34 = OpCompositeExtract %half %inputs 4
+         %35 = OpCompositeExtract %v3half %inputs 5
+               OpReturnValue %21
+               OpFunctionEnd
+       %main = OpFunction %void None %36
+         %39 = OpLabel
+         %41 = OpLoad %int %loc0_1
+         %42 = OpLoad %uint %loc1_1
+         %43 = OpLoad %float %loc2_1
+         %44 = OpLoad %v4float %loc3_1
+         %45 = OpLoad %half %loc4_1
+         %46 = OpLoad %v3half %loc5_1
+         %47 = OpCompositeConstruct %VertexInputs %41 %42 %43 %44 %45 %46
+         %40 = OpFunctionCall %v4float %main_inner %47
+               OpStore %value %40
+               OpStore %vertex_point_size %float_1
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..784cbc9
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_locations_struct_f16.wgsl.expected.wgsl
@@ -0,0 +1,27 @@
+enable f16;
+
+struct VertexInputs {
+  @location(0)
+  loc0 : i32,
+  @location(1)
+  loc1 : u32,
+  @location(2)
+  loc2 : f32,
+  @location(3)
+  loc3 : vec4<f32>,
+  @location(4)
+  loc4 : f16,
+  @location(5)
+  loc5 : vec3<f16>,
+}
+
+@vertex
+fn main(inputs : VertexInputs) -> @builtin(position) vec4<f32> {
+  let i : i32 = inputs.loc0;
+  let u : u32 = inputs.loc1;
+  let f : f32 = inputs.loc2;
+  let v : vec4<f32> = inputs.loc3;
+  let x : f16 = inputs.loc4;
+  let y : vec3<f16> = inputs.loc5;
+  return vec4<f32>();
+}
diff --git a/test/tint/shader_io/vertex_input_mixed_f16.wgsl b/test/tint/shader_io/vertex_input_mixed_f16.wgsl
new file mode 100644
index 0000000..d7963a2
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_mixed_f16.wgsl
@@ -0,0 +1,29 @@
+enable f16;
+
+struct VertexInputs0 {
+  @builtin(vertex_index) vertex_index : u32,
+  @location(0) loc0 : i32,
+};
+struct VertexInputs1 {
+  @location(2) loc2 : f32,
+  @location(3) loc3 : vec4<f32>,
+  @location(5) loc5 : vec3<f16>,
+};
+
+@vertex
+fn main(
+  inputs0 : VertexInputs0,
+  @location(1) loc1 : u32,
+  @builtin(instance_index) instance_index : u32,
+  inputs1 : VertexInputs1,
+  @location(4) loc4 : f16,
+) -> @builtin(position) vec4<f32> {
+  let foo : u32 = inputs0.vertex_index + instance_index;
+  let i : i32 = inputs0.loc0;
+  let u : u32 = loc1;
+  let f : f32 = inputs1.loc2;
+  let v : vec4<f32> = inputs1.loc3;
+  let x : f16 = loc4;
+  let y : vec3<f16> = inputs1.loc5;
+  return vec4<f32>();
+}
diff --git a/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..dfac345
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,42 @@
+struct VertexInputs0 {
+  uint vertex_index;
+  int loc0;
+};
+struct VertexInputs1 {
+  float loc2;
+  float4 loc3;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol_1 {
+  int loc0 : TEXCOORD0;
+  uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+  uint vertex_index : SV_VertexID;
+  uint instance_index : SV_InstanceID;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Position;
+};
+
+float4 main_inner(VertexInputs0 inputs0, uint loc1, uint instance_index, VertexInputs1 inputs1, float16_t loc4) {
+  const uint foo = (inputs0.vertex_index + instance_index);
+  const int i = inputs0.loc0;
+  const uint u = loc1;
+  const float f = inputs1.loc2;
+  const float4 v = inputs1.loc3;
+  const float16_t x = loc4;
+  const vector<float16_t, 3> y = inputs1.loc5;
+  return (0.0f).xxxx;
+}
+
+tint_symbol_2 main(tint_symbol_1 tint_symbol) {
+  const VertexInputs0 tint_symbol_3 = {tint_symbol.vertex_index, tint_symbol.loc0};
+  const VertexInputs1 tint_symbol_4 = {tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc5};
+  const float4 inner_result = main_inner(tint_symbol_3, tint_symbol.loc1, tint_symbol.instance_index, tint_symbol_4, tint_symbol.loc4);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
diff --git a/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..d5ef1cf
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,47 @@
+SKIP: FAILED
+
+struct VertexInputs0 {
+  uint vertex_index;
+  int loc0;
+};
+struct VertexInputs1 {
+  float loc2;
+  float4 loc3;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol_1 {
+  int loc0 : TEXCOORD0;
+  uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+  uint vertex_index : SV_VertexID;
+  uint instance_index : SV_InstanceID;
+};
+struct tint_symbol_2 {
+  float4 value : SV_Position;
+};
+
+float4 main_inner(VertexInputs0 inputs0, uint loc1, uint instance_index, VertexInputs1 inputs1, float16_t loc4) {
+  const uint foo = (inputs0.vertex_index + instance_index);
+  const int i = inputs0.loc0;
+  const uint u = loc1;
+  const float f = inputs1.loc2;
+  const float4 v = inputs1.loc3;
+  const float16_t x = loc4;
+  const vector<float16_t, 3> y = inputs1.loc5;
+  return (0.0f).xxxx;
+}
+
+tint_symbol_2 main(tint_symbol_1 tint_symbol) {
+  const VertexInputs0 tint_symbol_3 = {tint_symbol.vertex_index, tint_symbol.loc0};
+  const VertexInputs1 tint_symbol_4 = {tint_symbol.loc2, tint_symbol.loc3, tint_symbol.loc5};
+  const float4 inner_result = main_inner(tint_symbol_3, tint_symbol.loc1, tint_symbol.instance_index, tint_symbol_4, tint_symbol.loc4);
+  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x0000026E46098A20(8,10-18): error X3000: syntax error: unexpected token 'float16_t'
+
diff --git a/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.glsl b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..8d16b68
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.glsl
@@ -0,0 +1,41 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+layout(location = 0) in int loc0_1;
+layout(location = 1) in uint loc1_1;
+layout(location = 2) in float loc2_1;
+layout(location = 3) in vec4 loc3_1;
+layout(location = 5) in f16vec3 loc5_1;
+layout(location = 4) in float16_t loc4_1;
+struct VertexInputs0 {
+  uint vertex_index;
+  int loc0;
+};
+
+struct VertexInputs1 {
+  float loc2;
+  vec4 loc3;
+  f16vec3 loc5;
+};
+
+vec4 tint_symbol(VertexInputs0 inputs0, uint loc1, uint instance_index, VertexInputs1 inputs1, float16_t loc4) {
+  uint foo = (inputs0.vertex_index + instance_index);
+  int i = inputs0.loc0;
+  uint u = loc1;
+  float f = inputs1.loc2;
+  vec4 v = inputs1.loc3;
+  float16_t x = loc4;
+  f16vec3 y = inputs1.loc5;
+  return vec4(0.0f);
+}
+
+void main() {
+  gl_PointSize = 1.0;
+  VertexInputs0 tint_symbol_1 = VertexInputs0(uint(gl_VertexID), loc0_1);
+  VertexInputs1 tint_symbol_2 = VertexInputs1(loc2_1, loc3_1, loc5_1);
+  vec4 inner_result = tint_symbol(tint_symbol_1, loc1_1, uint(gl_InstanceID), tint_symbol_2, loc4_1);
+  gl_Position = inner_result;
+  gl_Position.y = -(gl_Position.y);
+  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
+  return;
+}
diff --git a/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.msl b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.msl
new file mode 100644
index 0000000..0140da6
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.msl
@@ -0,0 +1,47 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct VertexInputs0 {
+  uint vertex_index;
+  int loc0;
+};
+
+struct VertexInputs1 {
+  float loc2;
+  float4 loc3;
+  half3 loc5;
+};
+
+struct tint_symbol_2 {
+  int loc0 [[attribute(0)]];
+  uint loc1 [[attribute(1)]];
+  float loc2 [[attribute(2)]];
+  float4 loc3 [[attribute(3)]];
+  half loc4 [[attribute(4)]];
+  half3 loc5 [[attribute(5)]];
+};
+
+struct tint_symbol_3 {
+  float4 value [[position]];
+};
+
+float4 tint_symbol_inner(VertexInputs0 inputs0, uint loc1, uint instance_index, VertexInputs1 inputs1, half loc4) {
+  uint const foo = (inputs0.vertex_index + instance_index);
+  int const i = inputs0.loc0;
+  uint const u = loc1;
+  float const f = inputs1.loc2;
+  float4 const v = inputs1.loc3;
+  half const x = loc4;
+  half3 const y = inputs1.loc5;
+  return float4(0.0f);
+}
+
+vertex tint_symbol_3 tint_symbol(uint vertex_index [[vertex_id]], uint instance_index [[instance_id]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  VertexInputs0 const tint_symbol_4 = {.vertex_index=vertex_index, .loc0=tint_symbol_1.loc0};
+  VertexInputs1 const tint_symbol_5 = {.loc2=tint_symbol_1.loc2, .loc3=tint_symbol_1.loc3, .loc5=tint_symbol_1.loc5};
+  float4 const inner_result = tint_symbol_inner(tint_symbol_4, tint_symbol_1.loc1, instance_index, tint_symbol_5, tint_symbol_1.loc4);
+  tint_symbol_3 wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
diff --git a/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.spvasm b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..1627483
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.spvasm
@@ -0,0 +1,115 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 59
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %vertex_index_1 %loc0_1 %loc1_1 %instance_index_1 %loc2_1 %loc3_1 %loc5_1 %loc4_1 %value %vertex_point_size
+               OpName %vertex_index_1 "vertex_index_1"
+               OpName %loc0_1 "loc0_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %instance_index_1 "instance_index_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %value "value"
+               OpName %vertex_point_size "vertex_point_size"
+               OpName %VertexInputs0 "VertexInputs0"
+               OpMemberName %VertexInputs0 0 "vertex_index"
+               OpMemberName %VertexInputs0 1 "loc0"
+               OpName %VertexInputs1 "VertexInputs1"
+               OpMemberName %VertexInputs1 0 "loc2"
+               OpMemberName %VertexInputs1 1 "loc3"
+               OpMemberName %VertexInputs1 2 "loc5"
+               OpName %main_inner "main_inner"
+               OpName %inputs0 "inputs0"
+               OpName %loc1 "loc1"
+               OpName %instance_index "instance_index"
+               OpName %inputs1 "inputs1"
+               OpName %loc4 "loc4"
+               OpName %main "main"
+               OpDecorate %vertex_index_1 BuiltIn VertexIndex
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %instance_index_1 BuiltIn InstanceIndex
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %loc5_1 Location 5
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %value BuiltIn Position
+               OpDecorate %vertex_point_size BuiltIn PointSize
+               OpMemberDecorate %VertexInputs0 0 Offset 0
+               OpMemberDecorate %VertexInputs0 1 Offset 4
+               OpMemberDecorate %VertexInputs1 0 Offset 0
+               OpMemberDecorate %VertexInputs1 1 Offset 16
+               OpMemberDecorate %VertexInputs1 2 Offset 32
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%vertex_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+     %loc0_1 = OpVariable %_ptr_Input_int Input
+     %loc1_1 = OpVariable %_ptr_Input_uint Input
+%instance_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+     %loc2_1 = OpVariable %_ptr_Input_float Input
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+     %loc3_1 = OpVariable %_ptr_Input_v4float Input
+       %half = OpTypeFloat 16
+     %v3half = OpTypeVector %half 3
+%_ptr_Input_v3half = OpTypePointer Input %v3half
+     %loc5_1 = OpVariable %_ptr_Input_v3half Input
+%_ptr_Input_half = OpTypePointer Input %half
+     %loc4_1 = OpVariable %_ptr_Input_half Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %23 = OpConstantNull %v4float
+      %value = OpVariable %_ptr_Output_v4float Output %23
+%_ptr_Output_float = OpTypePointer Output %float
+         %26 = OpConstantNull %float
+%vertex_point_size = OpVariable %_ptr_Output_float Output %26
+%VertexInputs0 = OpTypeStruct %uint %int
+%VertexInputs1 = OpTypeStruct %float %v4float %v3half
+         %27 = OpTypeFunction %v4float %VertexInputs0 %uint %uint %VertexInputs1 %half
+       %void = OpTypeVoid
+         %43 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+ %main_inner = OpFunction %v4float None %27
+    %inputs0 = OpFunctionParameter %VertexInputs0
+       %loc1 = OpFunctionParameter %uint
+%instance_index = OpFunctionParameter %uint
+    %inputs1 = OpFunctionParameter %VertexInputs1
+       %loc4 = OpFunctionParameter %half
+         %36 = OpLabel
+         %37 = OpCompositeExtract %uint %inputs0 0
+         %38 = OpIAdd %uint %37 %instance_index
+         %39 = OpCompositeExtract %int %inputs0 1
+         %40 = OpCompositeExtract %float %inputs1 0
+         %41 = OpCompositeExtract %v4float %inputs1 1
+         %42 = OpCompositeExtract %v3half %inputs1 2
+               OpReturnValue %23
+               OpFunctionEnd
+       %main = OpFunction %void None %43
+         %46 = OpLabel
+         %48 = OpLoad %uint %vertex_index_1
+         %49 = OpLoad %int %loc0_1
+         %50 = OpCompositeConstruct %VertexInputs0 %48 %49
+         %51 = OpLoad %uint %loc1_1
+         %52 = OpLoad %uint %instance_index_1
+         %53 = OpLoad %float %loc2_1
+         %54 = OpLoad %v4float %loc3_1
+         %55 = OpLoad %v3half %loc5_1
+         %56 = OpCompositeConstruct %VertexInputs1 %53 %54 %55
+         %57 = OpLoad %half %loc4_1
+         %47 = OpFunctionCall %v4float %main_inner %50 %51 %52 %56 %57
+               OpStore %value %47
+               OpStore %vertex_point_size %float_1
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.wgsl b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..c87b17e
--- /dev/null
+++ b/test/tint/shader_io/vertex_input_mixed_f16.wgsl.expected.wgsl
@@ -0,0 +1,29 @@
+enable f16;
+
+struct VertexInputs0 {
+  @builtin(vertex_index)
+  vertex_index : u32,
+  @location(0)
+  loc0 : i32,
+}
+
+struct VertexInputs1 {
+  @location(2)
+  loc2 : f32,
+  @location(3)
+  loc3 : vec4<f32>,
+  @location(5)
+  loc5 : vec3<f16>,
+}
+
+@vertex
+fn main(inputs0 : VertexInputs0, @location(1) loc1 : u32, @builtin(instance_index) instance_index : u32, inputs1 : VertexInputs1, @location(4) loc4 : f16) -> @builtin(position) vec4<f32> {
+  let foo : u32 = (inputs0.vertex_index + instance_index);
+  let i : i32 = inputs0.loc0;
+  let u : u32 = loc1;
+  let f : f32 = inputs1.loc2;
+  let v : vec4<f32> = inputs1.loc3;
+  let x : f16 = loc4;
+  let y : vec3<f16> = inputs1.loc5;
+  return vec4<f32>();
+}
diff --git a/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl
new file mode 100644
index 0000000..6c9f91c
--- /dev/null
+++ b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl
@@ -0,0 +1,16 @@
+enable f16;
+
+struct VertexOutputs {
+  @location(0) @interpolate(flat) loc0 : i32,
+  @location(1) @interpolate(flat) loc1 : u32,
+  @location(2) loc2 : f32,
+  @location(3) loc3 : vec4<f32>,
+  @builtin(position) position : vec4<f32>,
+  @location(4) loc4 : f16,
+  @location(5) loc5 : vec3<f16>,
+};
+
+@vertex
+fn main() -> VertexOutputs {
+  return VertexOutputs(1, 1u, 1.0, vec4<f32>(1.0, 2.0, 3.0, 4.0), vec4<f32>(), 2.25h, vec3<f16>(3.0h, 5.0h, 8.0h));
+}
diff --git a/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.dxc.hlsl b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..32c2611
--- /dev/null
+++ b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.dxc.hlsl
@@ -0,0 +1,36 @@
+struct VertexOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float4 position;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+  float4 position : SV_Position;
+};
+
+VertexOutputs main_inner() {
+  const VertexOutputs tint_symbol_1 = {1, 1u, 1.0f, float4(1.0f, 2.0f, 3.0f, 4.0f), (0.0f).xxxx, float16_t(2.25h), vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h))};
+  return tint_symbol_1;
+}
+
+tint_symbol main() {
+  const VertexOutputs inner_result = main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.position = inner_result.position;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
diff --git a/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.fxc.hlsl b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8d3722d
--- /dev/null
+++ b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.fxc.hlsl
@@ -0,0 +1,41 @@
+SKIP: FAILED
+
+struct VertexOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float4 position;
+  float16_t loc4;
+  vector<float16_t, 3> loc5;
+};
+struct tint_symbol {
+  nointerpolation int loc0 : TEXCOORD0;
+  nointerpolation uint loc1 : TEXCOORD1;
+  float loc2 : TEXCOORD2;
+  float4 loc3 : TEXCOORD3;
+  float16_t loc4 : TEXCOORD4;
+  vector<float16_t, 3> loc5 : TEXCOORD5;
+  float4 position : SV_Position;
+};
+
+VertexOutputs main_inner() {
+  const VertexOutputs tint_symbol_1 = {1, 1u, 1.0f, float4(1.0f, 2.0f, 3.0f, 4.0f), (0.0f).xxxx, float16_t(2.25h), vector<float16_t, 3>(float16_t(3.0h), float16_t(5.0h), float16_t(8.0h))};
+  return tint_symbol_1;
+}
+
+tint_symbol main() {
+  const VertexOutputs inner_result = main_inner();
+  tint_symbol wrapper_result = (tint_symbol)0;
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.position = inner_result.position;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
+FXC validation failure:
+D:\Projects\RampUp\dawn\test\tint\shader_io\Shader@0x000002CD4C6B4280(7,3-11): error X3000: unrecognized identifier 'float16_t'
+
diff --git a/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.glsl b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.glsl
new file mode 100644
index 0000000..22881f0
--- /dev/null
+++ b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.glsl
@@ -0,0 +1,38 @@
+#version 310 es
+#extension GL_AMD_gpu_shader_half_float : require
+
+layout(location = 0) flat out int loc0_1;
+layout(location = 1) flat out uint loc1_1;
+layout(location = 2) out float loc2_1;
+layout(location = 3) out vec4 loc3_1;
+layout(location = 4) out float16_t loc4_1;
+layout(location = 5) out f16vec3 loc5_1;
+struct VertexOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  vec4 loc3;
+  vec4 position;
+  float16_t loc4;
+  f16vec3 loc5;
+};
+
+VertexOutputs tint_symbol() {
+  VertexOutputs tint_symbol_1 = VertexOutputs(1, 1u, 1.0f, vec4(1.0f, 2.0f, 3.0f, 4.0f), vec4(0.0f), 2.25hf, f16vec3(3.0hf, 5.0hf, 8.0hf));
+  return tint_symbol_1;
+}
+
+void main() {
+  gl_PointSize = 1.0;
+  VertexOutputs inner_result = tint_symbol();
+  loc0_1 = inner_result.loc0;
+  loc1_1 = inner_result.loc1;
+  loc2_1 = inner_result.loc2;
+  loc3_1 = inner_result.loc3;
+  gl_Position = inner_result.position;
+  loc4_1 = inner_result.loc4;
+  loc5_1 = inner_result.loc5;
+  gl_Position.y = -(gl_Position.y);
+  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
+  return;
+}
diff --git a/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.msl b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.msl
new file mode 100644
index 0000000..707636a
--- /dev/null
+++ b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.msl
@@ -0,0 +1,41 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct VertexOutputs {
+  int loc0;
+  uint loc1;
+  float loc2;
+  float4 loc3;
+  float4 position;
+  half loc4;
+  half3 loc5;
+};
+
+struct tint_symbol_1 {
+  int loc0 [[user(locn0)]] [[flat]];
+  uint loc1 [[user(locn1)]] [[flat]];
+  float loc2 [[user(locn2)]];
+  float4 loc3 [[user(locn3)]];
+  half loc4 [[user(locn4)]];
+  half3 loc5 [[user(locn5)]];
+  float4 position [[position]];
+};
+
+VertexOutputs tint_symbol_inner() {
+  VertexOutputs const tint_symbol_2 = VertexOutputs{.loc0=1, .loc1=1u, .loc2=1.0f, .loc3=float4(1.0f, 2.0f, 3.0f, 4.0f), .position=float4(0.0f), .loc4=2.25h, .loc5=half3(3.0h, 5.0h, 8.0h)};
+  return tint_symbol_2;
+}
+
+vertex tint_symbol_1 tint_symbol() {
+  VertexOutputs const inner_result = tint_symbol_inner();
+  tint_symbol_1 wrapper_result = {};
+  wrapper_result.loc0 = inner_result.loc0;
+  wrapper_result.loc1 = inner_result.loc1;
+  wrapper_result.loc2 = inner_result.loc2;
+  wrapper_result.loc3 = inner_result.loc3;
+  wrapper_result.position = inner_result.position;
+  wrapper_result.loc4 = inner_result.loc4;
+  wrapper_result.loc5 = inner_result.loc5;
+  return wrapper_result;
+}
+
diff --git a/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.spvasm b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.spvasm
new file mode 100644
index 0000000..79fd125
--- /dev/null
+++ b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.spvasm
@@ -0,0 +1,114 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 56
+; Schema: 0
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability UniformAndStorageBuffer16BitAccess
+               OpCapability StorageBuffer16BitAccess
+               OpCapability StorageInputOutput16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %loc0_1 %loc1_1 %loc2_1 %loc3_1 %position_1 %loc4_1 %loc5_1 %vertex_point_size
+               OpName %loc0_1 "loc0_1"
+               OpName %loc1_1 "loc1_1"
+               OpName %loc2_1 "loc2_1"
+               OpName %loc3_1 "loc3_1"
+               OpName %position_1 "position_1"
+               OpName %loc4_1 "loc4_1"
+               OpName %loc5_1 "loc5_1"
+               OpName %vertex_point_size "vertex_point_size"
+               OpName %VertexOutputs "VertexOutputs"
+               OpMemberName %VertexOutputs 0 "loc0"
+               OpMemberName %VertexOutputs 1 "loc1"
+               OpMemberName %VertexOutputs 2 "loc2"
+               OpMemberName %VertexOutputs 3 "loc3"
+               OpMemberName %VertexOutputs 4 "position"
+               OpMemberName %VertexOutputs 5 "loc4"
+               OpMemberName %VertexOutputs 6 "loc5"
+               OpName %main_inner "main_inner"
+               OpName %main "main"
+               OpDecorate %loc0_1 Location 0
+               OpDecorate %loc0_1 Flat
+               OpDecorate %loc1_1 Location 1
+               OpDecorate %loc1_1 Flat
+               OpDecorate %loc2_1 Location 2
+               OpDecorate %loc3_1 Location 3
+               OpDecorate %position_1 BuiltIn Position
+               OpDecorate %loc4_1 Location 4
+               OpDecorate %loc5_1 Location 5
+               OpDecorate %vertex_point_size BuiltIn PointSize
+               OpMemberDecorate %VertexOutputs 0 Offset 0
+               OpMemberDecorate %VertexOutputs 1 Offset 4
+               OpMemberDecorate %VertexOutputs 2 Offset 8
+               OpMemberDecorate %VertexOutputs 3 Offset 16
+               OpMemberDecorate %VertexOutputs 4 Offset 32
+               OpMemberDecorate %VertexOutputs 5 Offset 48
+               OpMemberDecorate %VertexOutputs 6 Offset 56
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+          %4 = OpConstantNull %int
+     %loc0_1 = OpVariable %_ptr_Output_int Output %4
+       %uint = OpTypeInt 32 0
+%_ptr_Output_uint = OpTypePointer Output %uint
+          %8 = OpConstantNull %uint
+     %loc1_1 = OpVariable %_ptr_Output_uint Output %8
+      %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+         %12 = OpConstantNull %float
+     %loc2_1 = OpVariable %_ptr_Output_float Output %12
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %16 = OpConstantNull %v4float
+     %loc3_1 = OpVariable %_ptr_Output_v4float Output %16
+ %position_1 = OpVariable %_ptr_Output_v4float Output %16
+       %half = OpTypeFloat 16
+%_ptr_Output_half = OpTypePointer Output %half
+         %21 = OpConstantNull %half
+     %loc4_1 = OpVariable %_ptr_Output_half Output %21
+     %v3half = OpTypeVector %half 3
+%_ptr_Output_v3half = OpTypePointer Output %v3half
+         %25 = OpConstantNull %v3half
+     %loc5_1 = OpVariable %_ptr_Output_v3half Output %25
+%vertex_point_size = OpVariable %_ptr_Output_float Output %12
+%VertexOutputs = OpTypeStruct %int %uint %float %v4float %v4float %half %v3half
+         %27 = OpTypeFunction %VertexOutputs
+      %int_1 = OpConstant %int 1
+     %uint_1 = OpConstant %uint 1
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
+         %37 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+%half_0x1_2p_1 = OpConstant %half 0x1.2p+1
+%half_0x1_8p_1 = OpConstant %half 0x1.8p+1
+%half_0x1_4p_2 = OpConstant %half 0x1.4p+2
+%half_0x1p_3 = OpConstant %half 0x1p+3
+         %42 = OpConstantComposite %v3half %half_0x1_8p_1 %half_0x1_4p_2 %half_0x1p_3
+         %43 = OpConstantComposite %VertexOutputs %int_1 %uint_1 %float_1 %37 %16 %half_0x1_2p_1 %42
+       %void = OpTypeVoid
+         %44 = OpTypeFunction %void
+ %main_inner = OpFunction %VertexOutputs None %27
+         %30 = OpLabel
+               OpReturnValue %43
+               OpFunctionEnd
+       %main = OpFunction %void None %44
+         %47 = OpLabel
+         %48 = OpFunctionCall %VertexOutputs %main_inner
+         %49 = OpCompositeExtract %int %48 0
+               OpStore %loc0_1 %49
+         %50 = OpCompositeExtract %uint %48 1
+               OpStore %loc1_1 %50
+         %51 = OpCompositeExtract %float %48 2
+               OpStore %loc2_1 %51
+         %52 = OpCompositeExtract %v4float %48 3
+               OpStore %loc3_1 %52
+         %53 = OpCompositeExtract %v4float %48 4
+               OpStore %position_1 %53
+         %54 = OpCompositeExtract %half %48 5
+               OpStore %loc4_1 %54
+         %55 = OpCompositeExtract %v3half %48 6
+               OpStore %loc5_1 %55
+               OpStore %vertex_point_size %float_1
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.wgsl b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.wgsl
new file mode 100644
index 0000000..46dc3f5
--- /dev/null
+++ b/test/tint/shader_io/vertex_output_locations_struct_f16.wgsl.expected.wgsl
@@ -0,0 +1,23 @@
+enable f16;
+
+struct VertexOutputs {
+  @location(0) @interpolate(flat)
+  loc0 : i32,
+  @location(1) @interpolate(flat)
+  loc1 : u32,
+  @location(2)
+  loc2 : f32,
+  @location(3)
+  loc3 : vec4<f32>,
+  @builtin(position)
+  position : vec4<f32>,
+  @location(4)
+  loc4 : f16,
+  @location(5)
+  loc5 : vec3<f16>,
+}
+
+@vertex
+fn main() -> VertexOutputs {
+  return VertexOutputs(1, 1u, 1.0, vec4<f32>(1.0, 2.0, 3.0, 4.0), vec4<f32>(), 2.25h, vec3<f16>(3.0h, 5.0h, 8.0h));
+}