diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fbf6b4c..3d4a309 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,8 +15,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Select Xcode 16.2 - run: sudo xcode-select -s /Applications/Xcode_16.2.app + - name: Select Xcode 16.4 + run: sudo xcode-select -s /Applications/Xcode_16.4.app - name: Show Xcode version run: xcodebuild -version diff --git a/Sources/SwiftSlang/SLGlobalSession.mm b/Sources/SwiftSlang/SLGlobalSession.mm index 421c0d6..9a43bc6 100644 --- a/Sources/SwiftSlang/SLGlobalSession.mm +++ b/Sources/SwiftSlang/SLGlobalSession.mm @@ -65,6 +65,9 @@ - (nullable SLSession *)createSessionWithDesc:(SLSessionDesc *)desc // Build search paths std::vector searchPaths; std::vector searchPathStrings; // Keep strings alive + NSUInteger searchPathCount = desc.searchPaths.count; + searchPathStrings.reserve(searchPathCount); + searchPaths.reserve(searchPathCount); for (NSString *path in desc.searchPaths) { searchPathStrings.push_back([path UTF8String]); searchPaths.push_back(searchPathStrings.back().c_str()); @@ -74,6 +77,10 @@ - (nullable SLSession *)createSessionWithDesc:(SLSessionDesc *)desc std::vector macros; std::vector macroNames; std::vector macroValues; + NSUInteger macroCount = desc.preprocessorMacros.count; + macroNames.reserve(macroCount); + macroValues.reserve(macroCount); + macros.reserve(macroCount); for (NSString *name in desc.preprocessorMacros) { NSString *value = desc.preprocessorMacros[name]; macroNames.push_back([name UTF8String]); diff --git a/Tests/SwiftSlangTests/SwiftSlangTests.swift b/Tests/SwiftSlangTests/SwiftSlangTests.swift index c4b341a..5eccb55 100644 --- a/Tests/SwiftSlangTests/SwiftSlangTests.swift +++ b/Tests/SwiftSlangTests/SwiftSlangTests.swift @@ -356,6 +356,110 @@ final class SwiftSlangTests: XCTestCase { XCTAssertEqual(texType.getResourceAccess(), .read) } + // MARK: - Preprocessor Macros + + func testPreprocessorMacroAffectsCompilation() throws { + let globalSession = try SLGlobalSession.create() + let profile = globalSession.findProfile("sm_5_0") + let targetDesc = SLTargetDesc(format: .metal, profile: profile) + + let source = """ + uniform float intensity; + [shader("fragment")] + float4 fragMain() : SV_Target { + #ifdef USE_RED + return float4(intensity, 0, 0, 1); + #else + return float4(0, 0, intensity, 1); + #endif + } + """ + + // With USE_RED defined + let sessionDescWithMacro = SLSessionDesc() + sessionDescWithMacro.targets = [targetDesc] + sessionDescWithMacro.preprocessorMacros = ["USE_RED": "1"] + let sessionWith = try globalSession.createSession(with: sessionDescWithMacro) + let moduleWith = try sessionWith.loadModule(fromSourceString: "Test", path: "", source: source) + let epWith = try moduleWith.entryPoint(at: 0) + let compositeWith = try sessionWith.createCompositeComponentType(with: moduleWith, entryPoints: [epWith]) + let linkedWith = try compositeWith.link() + let codeWith = try linkedWith.getTargetCode(0) + + // Without USE_RED defined + let sessionDescWithout = SLSessionDesc() + sessionDescWithout.targets = [targetDesc] + let sessionWithout = try globalSession.createSession(with: sessionDescWithout) + let moduleWithout = try sessionWithout.loadModule(fromSourceString: "Test", path: "", source: source) + let epWithout = try moduleWithout.entryPoint(at: 0) + let compositeWithout = try sessionWithout.createCompositeComponentType(with: moduleWithout, entryPoints: [epWithout]) + let linkedWithout = try compositeWithout.link() + let codeWithout = try linkedWithout.getTargetCode(0) + + // Both should compile successfully but produce different Metal code + XCTAssertGreaterThan(codeWith.count, 0) + XCTAssertGreaterThan(codeWithout.count, 0) + XCTAssertNotEqual(codeWith, codeWithout) + } + + func testMultiplePreprocessorMacros() throws { + let globalSession = try SLGlobalSession.create() + let profile = globalSession.findProfile("sm_5_0") + let targetDesc = SLTargetDesc(format: .metal, profile: profile) + let sessionDesc = SLSessionDesc() + sessionDesc.targets = [targetDesc] + sessionDesc.preprocessorMacros = [ + "RESOLUTION_X": "1920", + "RESOLUTION_Y": "1080", + "SCALE": "2", + ] + let session = try globalSession.createSession(with: sessionDesc) + + let source = """ + [shader("fragment")] + float4 fragMain() : SV_Target { + float x = RESOLUTION_X; + float y = RESOLUTION_Y; + float s = SCALE; + return float4(x, y, s, 1); + } + """ + let module = try session.loadModule(fromSourceString: "Test", path: "", source: source) + let entryPoint = try module.entryPoint(at: 0) + let composite = try session.createCompositeComponentType(with: module, entryPoints: [entryPoint]) + let linked = try composite.link() + let metalCode = try linked.getTargetCode(0) + XCTAssertGreaterThan(metalCode.count, 0) + } + + func testPreprocessorMacroWithValue() throws { + let globalSession = try SLGlobalSession.create() + let profile = globalSession.findProfile("sm_5_0") + let targetDesc = SLTargetDesc(format: .metal, profile: profile) + let sessionDesc = SLSessionDesc() + sessionDesc.targets = [targetDesc] + sessionDesc.preprocessorMacros = ["CHANNEL_COUNT": "3"] + let session = try globalSession.createSession(with: sessionDesc) + + let source = """ + uniform float values[CHANNEL_COUNT]; + [shader("fragment")] + float4 fragMain() : SV_Target { return float4(values[0], values[1], values[2], 1); } + """ + let module = try session.loadModule(fromSourceString: "Test", path: "", source: source) + let entryPoint = try module.entryPoint(at: 0) + let composite = try session.createCompositeComponentType(with: module, entryPoints: [entryPoint]) + let linked = try composite.link() + let params = try linked.getShaderParameters(0) + + let valuesParam = params.first { $0.name == "values" } + XCTAssertNotNil(valuesParam) + let typeLayout = try XCTUnwrap(valuesParam?.typeLayout) + let type = try XCTUnwrap(typeLayout.getType()) + XCTAssertEqual(type.getKind(), .array) + XCTAssertEqual(type.getElementCount(), 3) + } + // MARK: - Entry Point func testComputeEntryPoint() throws {